home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-9.10-netbook-remix-PL.iso / casper / filesystem.squashfs / usr / lib / pymodules / python2.6 / feedparser.pyc (.txt) < prev    next >
Python Compiled Bytecode  |  2009-11-02  |  100KB  |  3,067 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.6)
  3.  
  4. '''Universal feed parser
  5.  
  6. Handles RSS 0.9x, RSS 1.0, RSS 2.0, CDF, Atom 0.3, and Atom 1.0 feeds
  7.  
  8. Visit http://feedparser.org/ for the latest version
  9. Visit http://feedparser.org/docs/ for the latest documentation
  10.  
  11. Required: Python 2.1 or later
  12. Recommended: Python 2.3 or later
  13. Recommended: CJKCodecs and iconv_codec <http://cjkpython.i18n.org/>
  14. '''
  15. __version__ = '4.1'
  16. __license__ = "Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice,\n  this list of conditions and the following disclaimer.\n* Redistributions in binary form must reproduce the above copyright notice,\n  this list of conditions and the following disclaimer in the documentation\n  and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS'\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\nLIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\nCONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\nSUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\nINTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\nCONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\nARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGE."
  17. __author__ = 'Mark Pilgrim <http://diveintomark.org/>'
  18. __contributors__ = [
  19.     'Jason Diamond <http://injektilo.org/>',
  20.     'John Beimler <http://john.beimler.org/>',
  21.     'Fazal Majid <http://www.majid.info/mylos/weblog/>',
  22.     'Aaron Swartz <http://aaronsw.com/>',
  23.     'Kevin Marks <http://epeus.blogspot.com/>']
  24. _debug = 0
  25. USER_AGENT = 'UniversalFeedParser/%s +http://feedparser.org/' % __version__
  26. ACCEPT_HEADER = 'application/atom+xml,application/rdf+xml,application/rss+xml,application/x-netcdf,application/xml;q=0.9,text/xml;q=0.2,*/*;q=0.1'
  27. PREFERRED_XML_PARSERS = [
  28.     'drv_libxml2']
  29. TIDY_MARKUP = 0
  30. PREFERRED_TIDY_INTERFACES = [
  31.     'uTidy',
  32.     'mxTidy']
  33. import sgmllib
  34. import re
  35. import sys
  36. import copy
  37. import urlparse
  38. import time
  39. import rfc822
  40. import types
  41. import cgi
  42. import urllib
  43. import urllib2
  44.  
  45. try:
  46.     from cStringIO import StringIO as _StringIO
  47. except:
  48.     from StringIO import StringIO as _StringIO
  49.  
  50.  
  51. try:
  52.     import gzip
  53. except:
  54.     gzip = None
  55.  
  56.  
  57. try:
  58.     import zlib
  59. except:
  60.     zlib = None
  61.  
  62.  
  63. try:
  64.     import xml.sax as xml
  65.     xml.sax.make_parser(PREFERRED_XML_PARSERS)
  66.     from xml.sax.saxutils import escape as _xmlescape
  67.     _XML_AVAILABLE = 1
  68. except:
  69.     _XML_AVAILABLE = 0
  70.     
  71.     def _xmlescape(data):
  72.         data = data.replace('&', '&')
  73.         data = data.replace('>', '>')
  74.         data = data.replace('<', '<')
  75.         return data
  76.  
  77.  
  78.  
  79. try:
  80.     import base64
  81.     import binascii
  82. except:
  83.     base64 = binascii = None
  84.  
  85.  
  86. try:
  87.     import cjkcodecs.aliases as cjkcodecs
  88. except:
  89.     pass
  90.  
  91.  
  92. try:
  93.     import iconv_codec
  94. except:
  95.     pass
  96.  
  97.  
  98. try:
  99.     import chardet
  100.     if _debug:
  101.         import chardet.constants as chardet
  102.         chardet.constants._debug = 1
  103. except:
  104.     chardet = None
  105.  
  106.  
  107. class ThingsNobodyCaresAboutButMe(Exception):
  108.     pass
  109.  
  110.  
  111. class CharacterEncodingOverride(ThingsNobodyCaresAboutButMe):
  112.     pass
  113.  
  114.  
  115. class CharacterEncodingUnknown(ThingsNobodyCaresAboutButMe):
  116.     pass
  117.  
  118.  
  119. class NonXMLContentType(ThingsNobodyCaresAboutButMe):
  120.     pass
  121.  
  122.  
  123. class UndeclaredNamespace(Exception):
  124.     pass
  125.  
  126. sgmllib.tagfind = re.compile('[a-zA-Z][-_.:a-zA-Z0-9]*')
  127. sgmllib.special = re.compile('<!')
  128. sgmllib.charref = re.compile('&#(x?[0-9A-Fa-f]+)[^0-9A-Fa-f]')
  129. SUPPORTED_VERSIONS = {
  130.     '': 'unknown',
  131.     'rss090': 'RSS 0.90',
  132.     'rss091n': 'RSS 0.91 (Netscape)',
  133.     'rss091u': 'RSS 0.91 (Userland)',
  134.     'rss092': 'RSS 0.92',
  135.     'rss093': 'RSS 0.93',
  136.     'rss094': 'RSS 0.94',
  137.     'rss20': 'RSS 2.0',
  138.     'rss10': 'RSS 1.0',
  139.     'rss': 'RSS (unknown version)',
  140.     'atom01': 'Atom 0.1',
  141.     'atom02': 'Atom 0.2',
  142.     'atom03': 'Atom 0.3',
  143.     'atom10': 'Atom 1.0',
  144.     'atom': 'Atom (unknown version)',
  145.     'cdf': 'CDF',
  146.     'hotrss': 'Hot RSS' }
  147.  
  148. try:
  149.     UserDict = dict
  150. except NameError:
  151.     from UserDict import UserDict
  152.     
  153.     def dict(aList):
  154.         rc = { }
  155.         for k, v in aList:
  156.             rc[k] = v
  157.         
  158.         return rc
  159.  
  160.  
  161.  
  162. class FeedParserDict(UserDict):
  163.     keymap = {
  164.         'channel': 'feed',
  165.         'items': 'entries',
  166.         'guid': 'id',
  167.         'date': 'updated',
  168.         'date_parsed': 'updated_parsed',
  169.         'description': [
  170.             'subtitle',
  171.             'summary'],
  172.         'url': [
  173.             'href'],
  174.         'modified': 'updated',
  175.         'modified_parsed': 'updated_parsed',
  176.         'issued': 'published',
  177.         'issued_parsed': 'published_parsed',
  178.         'copyright': 'rights',
  179.         'copyright_detail': 'rights_detail',
  180.         'tagline': 'subtitle',
  181.         'tagline_detail': 'subtitle_detail' }
  182.     
  183.     def __getitem__(self, key):
  184.         if key == 'category':
  185.             return UserDict.__getitem__(self, 'tags')[0]['term']
  186.         if key == 'categories':
  187.             return [ (tag['scheme'], tag['term']) for tag in UserDict.__getitem__(self, 'tags') ]
  188.         realkey = self.keymap.get(key, key)
  189.         if UserDict.has_key(self, key):
  190.             return UserDict.__getitem__(self, key)
  191.         return UserDict.__getitem__(self, realkey)
  192.  
  193.     
  194.     def __setitem__(self, key, value):
  195.         for k in self.keymap.keys():
  196.             if key == k:
  197.                 key = self.keymap[k]
  198.                 if type(key) == types.ListType:
  199.                     key = key[0]
  200.                 
  201.             type(key) == types.ListType
  202.         
  203.         return UserDict.__setitem__(self, key, value)
  204.  
  205.     
  206.     def get(self, key, default = None):
  207.         if self.has_key(key):
  208.             return self[key]
  209.         return default
  210.  
  211.     
  212.     def setdefault(self, key, value):
  213.         if not self.has_key(key):
  214.             self[key] = value
  215.         
  216.         return self[key]
  217.  
  218.     
  219.     def has_key(self, key):
  220.         
  221.         try:
  222.             if not hasattr(self, key):
  223.                 pass
  224.             return UserDict.has_key(self, key)
  225.         except AttributeError:
  226.             return False
  227.  
  228.  
  229.     
  230.     def __getattr__(self, key):
  231.         
  232.         try:
  233.             return self.__dict__[key]
  234.         except KeyError:
  235.             pass
  236.  
  237.         
  238.         try:
  239.             if not not key.startswith('_'):
  240.                 raise AssertionError
  241.             return self.__getitem__(key)
  242.         except:
  243.             raise AttributeError, "object has no attribute '%s'" % key
  244.  
  245.  
  246.     
  247.     def __setattr__(self, key, value):
  248.         if key.startswith('_') or key == 'data':
  249.             self.__dict__[key] = value
  250.         else:
  251.             return self.__setitem__(key, value)
  252.         return key == 'data'
  253.  
  254.     
  255.     def __contains__(self, key):
  256.         return self.has_key(key)
  257.  
  258.  
  259.  
  260. def zopeCompatibilityHack():
  261.     global FeedParserDict
  262.     del FeedParserDict
  263.     
  264.     def FeedParserDict(aDict = None):
  265.         rc = { }
  266.         if aDict:
  267.             rc.update(aDict)
  268.         
  269.         return rc
  270.  
  271.  
  272. _ebcdic_to_ascii_map = None
  273.  
  274. def _ebcdic_to_ascii(s):
  275.     global _ebcdic_to_ascii_map
  276.     if not _ebcdic_to_ascii_map:
  277.         emap = (0, 1, 2, 3, 156, 9, 134, 127, 151, 141, 142, 11, 12, 13, 14, 15, 16, 17, 18, 19, 157, 133, 8, 135, 24, 25, 146, 143, 28, 29, 30, 31, 128, 129, 130, 131, 132, 10, 23, 27, 136, 137, 138, 139, 140, 5, 6, 7, 144, 145, 22, 147, 148, 149, 150, 4, 152, 153, 154, 155, 20, 21, 158, 26, 32, 160, 161, 162, 163, 164, 165, 166, 167, 168, 91, 46, 60, 40, 43, 33, 38, 169, 170, 171, 172, 173, 174, 175, 176, 177, 93, 36, 42, 41, 59, 94, 45, 47, 178, 179, 180, 181, 182, 183, 184, 185, 124, 44, 37, 95, 62, 63, 186, 187, 188, 189, 190, 191, 192, 193, 194, 96, 58, 35, 64, 39, 61, 34, 195, 97, 98, 99, 100, 101, 102, 103, 104, 105, 196, 197, 198, 199, 200, 201, 202, 106, 107, 108, 109, 110, 111, 112, 113, 114, 203, 204, 205, 206, 207, 208, 209, 126, 115, 116, 117, 118, 119, 120, 121, 122, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 123, 65, 66, 67, 68, 69, 70, 71, 72, 73, 232, 233, 234, 235, 236, 237, 125, 74, 75, 76, 77, 78, 79, 80, 81, 82, 238, 239, 240, 241, 242, 243, 92, 159, 83, 84, 85, 86, 87, 88, 89, 90, 244, 245, 246, 247, 248, 249, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 250, 251, 252, 253, 254, 255)
  278.         import string as string
  279.         _ebcdic_to_ascii_map = string.maketrans(''.join(map(chr, range(256))), ''.join(map(chr, emap)))
  280.     
  281.     return s.translate(_ebcdic_to_ascii_map)
  282.  
  283. _urifixer = re.compile('^([A-Za-z][A-Za-z0-9+-.]*://)(/*)(.*?)')
  284.  
  285. def _urljoin(base, uri):
  286.     uri = _urifixer.sub('\\1\\3', uri)
  287.     return urlparse.urljoin(base, uri)
  288.  
  289.  
  290. class _FeedParserMixin:
  291.     namespaces = {
  292.         '': '',
  293.         'http://backend.userland.com/rss': '',
  294.         'http://blogs.law.harvard.edu/tech/rss': '',
  295.         'http://purl.org/rss/1.0/': '',
  296.         'http://my.netscape.com/rdf/simple/0.9/': '',
  297.         'http://example.com/newformat#': '',
  298.         'http://example.com/necho': '',
  299.         'http://purl.org/echo/': '',
  300.         'uri/of/echo/namespace#': '',
  301.         'http://purl.org/pie/': '',
  302.         'http://purl.org/atom/ns#': '',
  303.         'http://www.w3.org/2005/Atom': '',
  304.         'http://purl.org/rss/1.0/modules/rss091#': '',
  305.         'http://webns.net/mvcb/': 'admin',
  306.         'http://purl.org/rss/1.0/modules/aggregation/': 'ag',
  307.         'http://purl.org/rss/1.0/modules/annotate/': 'annotate',
  308.         'http://media.tangent.org/rss/1.0/': 'audio',
  309.         'http://backend.userland.com/blogChannelModule': 'blogChannel',
  310.         'http://web.resource.org/cc/': 'cc',
  311.         'http://backend.userland.com/creativeCommonsRssModule': 'creativeCommons',
  312.         'http://purl.org/rss/1.0/modules/company': 'co',
  313.         'http://purl.org/rss/1.0/modules/content/': 'content',
  314.         'http://my.theinfo.org/changed/1.0/rss/': 'cp',
  315.         'http://purl.org/dc/elements/1.1/': 'dc',
  316.         'http://purl.org/dc/terms/': 'dcterms',
  317.         'http://purl.org/rss/1.0/modules/email/': 'email',
  318.         'http://purl.org/rss/1.0/modules/event/': 'ev',
  319.         'http://rssnamespace.org/feedburner/ext/1.0': 'feedburner',
  320.         'http://freshmeat.net/rss/fm/': 'fm',
  321.         'http://xmlns.com/foaf/0.1/': 'foaf',
  322.         'http://www.w3.org/2003/01/geo/wgs84_pos#': 'geo',
  323.         'http://postneo.com/icbm/': 'icbm',
  324.         'http://purl.org/rss/1.0/modules/image/': 'image',
  325.         'http://www.itunes.com/DTDs/PodCast-1.0.dtd': 'itunes',
  326.         'http://example.com/DTDs/PodCast-1.0.dtd': 'itunes',
  327.         'http://purl.org/rss/1.0/modules/link/': 'l',
  328.         'http://search.yahoo.com/mrss': 'media',
  329.         'http://madskills.com/public/xml/rss/module/pingback/': 'pingback',
  330.         'http://prismstandard.org/namespaces/1.2/basic/': 'prism',
  331.         'http://www.w3.org/1999/02/22-rdf-syntax-ns#': 'rdf',
  332.         'http://www.w3.org/2000/01/rdf-schema#': 'rdfs',
  333.         'http://purl.org/rss/1.0/modules/reference/': 'ref',
  334.         'http://purl.org/rss/1.0/modules/richequiv/': 'reqv',
  335.         'http://purl.org/rss/1.0/modules/search/': 'search',
  336.         'http://purl.org/rss/1.0/modules/slash/': 'slash',
  337.         'http://schemas.xmlsoap.org/soap/envelope/': 'soap',
  338.         'http://purl.org/rss/1.0/modules/servicestatus/': 'ss',
  339.         'http://hacks.benhammersley.com/rss/streaming/': 'str',
  340.         'http://purl.org/rss/1.0/modules/subscription/': 'sub',
  341.         'http://purl.org/rss/1.0/modules/syndication/': 'sy',
  342.         'http://purl.org/rss/1.0/modules/taxonomy/': 'taxo',
  343.         'http://purl.org/rss/1.0/modules/threading/': 'thr',
  344.         'http://purl.org/rss/1.0/modules/textinput/': 'ti',
  345.         'http://madskills.com/public/xml/rss/module/trackback/': 'trackback',
  346.         'http://wellformedweb.org/commentAPI/': 'wfw',
  347.         'http://purl.org/rss/1.0/modules/wiki/': 'wiki',
  348.         'http://www.w3.org/1999/xhtml': 'xhtml',
  349.         'http://www.w3.org/XML/1998/namespace': 'xml',
  350.         'http://schemas.pocketsoap.com/rss/myDescModule/': 'szf' }
  351.     _matchnamespaces = { }
  352.     can_be_relative_uri = [
  353.         'link',
  354.         'id',
  355.         'wfw_comment',
  356.         'wfw_commentrss',
  357.         'docs',
  358.         'url',
  359.         'href',
  360.         'comments',
  361.         'license',
  362.         'icon',
  363.         'logo']
  364.     can_contain_relative_uris = [
  365.         'content',
  366.         'title',
  367.         'summary',
  368.         'info',
  369.         'tagline',
  370.         'subtitle',
  371.         'copyright',
  372.         'rights',
  373.         'description']
  374.     can_contain_dangerous_markup = [
  375.         'content',
  376.         'title',
  377.         'summary',
  378.         'info',
  379.         'tagline',
  380.         'subtitle',
  381.         'copyright',
  382.         'rights',
  383.         'description']
  384.     html_types = [
  385.         'text/html',
  386.         'application/xhtml+xml']
  387.     
  388.     def __init__(self, baseuri = None, baselang = None, encoding = 'utf-8'):
  389.         if _debug:
  390.             sys.stderr.write('initializing FeedParser\n')
  391.         
  392.         if not self._matchnamespaces:
  393.             for k, v in self.namespaces.items():
  394.                 self._matchnamespaces[k.lower()] = v
  395.             
  396.         
  397.         self.feeddata = FeedParserDict()
  398.         self.encoding = encoding
  399.         self.entries = []
  400.         self.version = ''
  401.         self.namespacesInUse = { }
  402.         self.infeed = 0
  403.         self.inentry = 0
  404.         self.incontent = 0
  405.         self.intextinput = 0
  406.         self.inimage = 0
  407.         self.inauthor = 0
  408.         self.incontributor = 0
  409.         self.inpublisher = 0
  410.         self.insource = 0
  411.         self.sourcedata = FeedParserDict()
  412.         self.contentparams = FeedParserDict()
  413.         self._summaryKey = None
  414.         self.namespacemap = { }
  415.         self.elementstack = []
  416.         self.basestack = []
  417.         self.langstack = []
  418.         if not baseuri:
  419.             pass
  420.         self.baseuri = ''
  421.         if not baselang:
  422.             pass
  423.         self.lang = None
  424.         if baselang:
  425.             self.feeddata['language'] = baselang
  426.         
  427.  
  428.     
  429.     def unknown_starttag(self, tag, attrs):
  430.         if _debug:
  431.             sys.stderr.write('start %s with %s\n' % (tag, attrs))
  432.         
  433.         attrs = [ (k.lower(), v) for k, v in attrs ]
  434.         attrs = [ (k, v) for k, v in attrs ]
  435.         attrsD = dict(attrs)
  436.         if not attrsD.get('xml:base', attrsD.get('base')):
  437.             pass
  438.         baseuri = self.baseuri
  439.         self.baseuri = _urljoin(self.baseuri, baseuri)
  440.         lang = attrsD.get('xml:lang', attrsD.get('lang'))
  441.         if lang == '':
  442.             lang = None
  443.         elif lang is None:
  444.             lang = self.lang
  445.         
  446.         self.lang = lang
  447.         self.basestack.append(self.baseuri)
  448.         self.langstack.append(lang)
  449.         for prefix, uri in attrs:
  450.             if prefix.startswith('xmlns:'):
  451.                 self.trackNamespace(prefix[6:], uri)
  452.                 continue
  453.             None if lang else []
  454.             if prefix == 'xmlns':
  455.                 self.trackNamespace(None, uri)
  456.                 continue
  457.         
  458.         if self.incontent and self.contentparams.has_key('type') and not self.contentparams.get('type', 'xml').endswith('xml'):
  459.             self.contentparams['type'] = 'application/xhtml+xml'
  460.         
  461.         if self.incontent and self.contentparams.get('type') == 'application/xhtml+xml':
  462.             tag = tag.split(':')[-1]
  463.             return tag(''.join % ([], []([ ' %s="%s"' % t for t in attrs ])), escape = 0)
  464.         if tag.find(':') != -1:
  465.             (prefix, suffix) = tag.split(':', 1)
  466.         else:
  467.             prefix = ''
  468.             suffix = tag
  469.         prefix = self.namespacemap.get(prefix, prefix)
  470.         if prefix:
  471.             prefix = prefix + '_'
  472.         
  473.         if not prefix and tag not in ('title', 'link', 'description', 'name'):
  474.             self.intextinput = 0
  475.         
  476.         if not prefix and tag not in ('title', 'link', 'description', 'url', 'href', 'width', 'height'):
  477.             self.inimage = 0
  478.         
  479.         methodname = '_start_' + prefix + suffix
  480.         
  481.         try:
  482.             method = getattr(self, methodname)
  483.             return method(attrsD)
  484.         except AttributeError:
  485.             return self.push(prefix + suffix, 1)
  486.  
  487.  
  488.     
  489.     def unknown_endtag(self, tag):
  490.         if _debug:
  491.             sys.stderr.write('end %s\n' % tag)
  492.         
  493.         if tag.find(':') != -1:
  494.             (prefix, suffix) = tag.split(':', 1)
  495.         else:
  496.             prefix = ''
  497.             suffix = tag
  498.         prefix = self.namespacemap.get(prefix, prefix)
  499.         if prefix:
  500.             prefix = prefix + '_'
  501.         
  502.         methodname = '_end_' + prefix + suffix
  503.         
  504.         try:
  505.             method = getattr(self, methodname)
  506.             method()
  507.         except AttributeError:
  508.             self.pop(prefix + suffix)
  509.  
  510.         if self.incontent and self.contentparams.has_key('type') and not self.contentparams.get('type', 'xml').endswith('xml'):
  511.             self.contentparams['type'] = 'application/xhtml+xml'
  512.         
  513.         if self.incontent and self.contentparams.get('type') == 'application/xhtml+xml':
  514.             tag = tag.split(':')[-1]
  515.             self.handle_data('</%s>' % tag, escape = 0)
  516.         
  517.         if self.basestack:
  518.             self.basestack.pop()
  519.             if self.basestack and self.basestack[-1]:
  520.                 self.baseuri = self.basestack[-1]
  521.             
  522.         
  523.         if self.langstack:
  524.             self.langstack.pop()
  525.             if self.langstack:
  526.                 self.lang = self.langstack[-1]
  527.             
  528.         
  529.  
  530.     
  531.     def handle_charref(self, ref):
  532.         if not self.elementstack:
  533.             return None
  534.         ref = ref.lower()
  535.         if ref in ('34', '38', '39', '60', '62', 'x22', 'x26', 'x27', 'x3c', 'x3e'):
  536.             text = '&#%s;' % ref
  537.         elif ref[0] == 'x':
  538.             c = int(ref[1:], 16)
  539.         else:
  540.             c = int(ref)
  541.         text = unichr(c).encode('utf-8')
  542.         self.elementstack[-1][2].append(text)
  543.  
  544.     
  545.     def handle_entityref(self, ref):
  546.         if not self.elementstack:
  547.             return None
  548.         if _debug:
  549.             sys.stderr.write('entering handle_entityref with %s\n' % ref)
  550.         
  551.         if ref in ('lt', 'gt', 'quot', 'amp', 'apos'):
  552.             text = '&%s;' % ref
  553.         else:
  554.             
  555.             def name2cp(k):
  556.                 import htmlentitydefs as htmlentitydefs
  557.                 if hasattr(htmlentitydefs, 'name2codepoint'):
  558.                     return htmlentitydefs.name2codepoint[k]
  559.                 k = htmlentitydefs.entitydefs[k]
  560.                 if k.startswith('&#') and k.endswith(';'):
  561.                     return int(k[2:-1])
  562.                 return ord(k)
  563.  
  564.             
  565.             try:
  566.                 name2cp(ref)
  567.             except KeyError:
  568.                 text = '&%s;' % ref
  569.  
  570.             text = unichr(name2cp(ref)).encode('utf-8')
  571.         self.elementstack[-1][2].append(text)
  572.  
  573.     
  574.     def handle_data(self, text, escape = 1):
  575.         if not self.elementstack:
  576.             return None
  577.         if escape and self.contentparams.get('type') == 'application/xhtml+xml':
  578.             text = _xmlescape(text)
  579.         
  580.         self.elementstack[-1][2].append(text)
  581.  
  582.     
  583.     def handle_comment(self, text):
  584.         pass
  585.  
  586.     
  587.     def handle_pi(self, text):
  588.         pass
  589.  
  590.     
  591.     def handle_decl(self, text):
  592.         pass
  593.  
  594.     
  595.     def parse_declaration(self, i):
  596.         if _debug:
  597.             sys.stderr.write('entering parse_declaration\n')
  598.         
  599.         if self.rawdata[i:i + 9] == '<![CDATA[':
  600.             k = self.rawdata.find(']]>', i)
  601.             if k == -1:
  602.                 k = len(self.rawdata)
  603.             
  604.             self.handle_data(_xmlescape(self.rawdata[i + 9:k]), 0)
  605.             return k + 3
  606.         k = self.rawdata.find('>', i)
  607.         return k + 1
  608.  
  609.     
  610.     def mapContentType(self, contentType):
  611.         contentType = contentType.lower()
  612.         if contentType == 'text' or contentType == 'plain':
  613.             contentType = 'text/plain'
  614.         elif contentType == 'html':
  615.             contentType = 'text/html'
  616.         elif contentType == 'xhtml':
  617.             contentType = 'application/xhtml+xml'
  618.         
  619.         return contentType
  620.  
  621.     
  622.     def trackNamespace(self, prefix, uri):
  623.         loweruri = uri.lower()
  624.         if (prefix, loweruri) == (None, 'http://my.netscape.com/rdf/simple/0.9/') and not (self.version):
  625.             self.version = 'rss090'
  626.         
  627.         if loweruri == 'http://purl.org/rss/1.0/' and not (self.version):
  628.             self.version = 'rss10'
  629.         
  630.         if loweruri == 'http://www.w3.org/2005/atom' and not (self.version):
  631.             self.version = 'atom10'
  632.         
  633.         if loweruri.find('backend.userland.com/rss') != -1:
  634.             uri = 'http://backend.userland.com/rss'
  635.             loweruri = uri
  636.         
  637.         uri[self.namespacesInUse if self._matchnamespaces.has_key(loweruri) else ''] = None
  638.  
  639.     
  640.     def resolveURI(self, uri):
  641.         if not self.baseuri:
  642.             pass
  643.         return _urljoin('', uri)
  644.  
  645.     
  646.     def decodeEntities(self, element, data):
  647.         return data
  648.  
  649.     
  650.     def push(self, element, expectingText):
  651.         self.elementstack.append([
  652.             element,
  653.             expectingText,
  654.             []])
  655.  
  656.     
  657.     def pop(self, element, stripWhitespace = 1):
  658.         if not self.elementstack:
  659.             return None
  660.         if self.elementstack[-1][0] != element:
  661.             return None
  662.         (element, expectingText, pieces) = self.elementstack.pop()
  663.         output = ''.join(pieces)
  664.         expectingText if not expectingText else expectingText
  665.         if element in self.can_be_relative_uri and output:
  666.             output = self.resolveURI(output)
  667.         
  668.         if not self.contentparams.get('base64', 0):
  669.             output = self.decodeEntities(element, output)
  670.         
  671.         
  672.         try:
  673.             del self.contentparams['mode']
  674.         except KeyError:
  675.             pass
  676.  
  677.         
  678.         try:
  679.             del self.contentparams['base64']
  680.         except KeyError:
  681.             pass
  682.  
  683.         if self.mapContentType(self.contentparams.get('type', 'text/html')) in self.html_types:
  684.             if element in self.can_contain_relative_uris:
  685.                 output = _resolveRelativeURIs(output, self.baseuri, self.encoding)
  686.             
  687.         
  688.         if self.mapContentType(self.contentparams.get('type', 'text/html')) in self.html_types:
  689.             if element in self.can_contain_dangerous_markup:
  690.                 output = _sanitizeHTML(output, self.encoding)
  691.             
  692.         
  693.         if self.encoding and type(output) != type(u''):
  694.             
  695.             try:
  696.                 output = unicode(output, self.encoding)
  697.  
  698.         
  699.         if element == 'category':
  700.             return output
  701.         if self.inentry and not (self.insource):
  702.             if element == 'content':
  703.                 self.entries[-1].setdefault(element, [])
  704.                 contentparams = copy.deepcopy(self.contentparams)
  705.                 contentparams['value'] = output
  706.                 self.entries[-1][element].append(contentparams)
  707.             elif element == 'link':
  708.                 self.entries[-1][element] = output
  709.                 if output:
  710.                     self.entries[-1]['links'][-1]['href'] = output
  711.                 
  712.             elif element == 'description':
  713.                 element = 'summary'
  714.             
  715.             self.entries[-1][element] = output
  716.             if self.incontent:
  717.                 contentparams = copy.deepcopy(self.contentparams)
  718.                 contentparams['value'] = output
  719.                 self.entries[-1][element + '_detail'] = contentparams
  720.             
  721.         elif (self.infeed or self.insource) and not (self.intextinput) and not (self.inimage):
  722.             context = self._getContext()
  723.             if element == 'description':
  724.                 element = 'subtitle'
  725.             
  726.             context[element] = output
  727.             if element == 'link':
  728.                 context['links'][-1]['href'] = output
  729.             elif self.incontent:
  730.                 contentparams = copy.deepcopy(self.contentparams)
  731.                 contentparams['value'] = output
  732.                 context[element + '_detail'] = contentparams
  733.             
  734.         
  735.         return output
  736.  
  737.     
  738.     def pushContent(self, tag, attrsD, defaultContentType, expectingText):
  739.         self.incontent += 1
  740.         self.contentparams = FeedParserDict({
  741.             'type': self.mapContentType(attrsD.get('type', defaultContentType)),
  742.             'language': self.lang,
  743.             'base': self.baseuri })
  744.         self.contentparams['base64'] = self._isBase64(attrsD, self.contentparams)
  745.         self.push(tag, expectingText)
  746.  
  747.     
  748.     def popContent(self, tag):
  749.         value = self.pop(tag)
  750.         self.incontent -= 1
  751.         self.contentparams.clear()
  752.         return value
  753.  
  754.     
  755.     def _mapToStandardPrefix(self, name):
  756.         colonpos = name.find(':')
  757.         if colonpos != -1:
  758.             prefix = name[:colonpos]
  759.             suffix = name[colonpos + 1:]
  760.             prefix = self.namespacemap.get(prefix, prefix)
  761.             name = prefix + ':' + suffix
  762.         
  763.         return name
  764.  
  765.     
  766.     def _getAttribute(self, attrsD, name):
  767.         return attrsD.get(self._mapToStandardPrefix(name))
  768.  
  769.     
  770.     def _isBase64(self, attrsD, contentparams):
  771.         if attrsD.get('mode', '') == 'base64':
  772.             return 1
  773.         if self.contentparams['type'].startswith('text/'):
  774.             return 0
  775.         if self.contentparams['type'].endswith('+xml'):
  776.             return 0
  777.         if self.contentparams['type'].endswith('/xml'):
  778.             return 0
  779.         return 1
  780.  
  781.     
  782.     def _itsAnHrefDamnIt(self, attrsD):
  783.         href = attrsD.get('url', attrsD.get('uri', attrsD.get('href', None)))
  784.         if href:
  785.             
  786.             try:
  787.                 del attrsD['url']
  788.             except KeyError:
  789.                 pass
  790.  
  791.             
  792.             try:
  793.                 del attrsD['uri']
  794.             except KeyError:
  795.                 pass
  796.  
  797.             attrsD['href'] = href
  798.         
  799.         return attrsD
  800.  
  801.     
  802.     def _save(self, key, value):
  803.         context = self._getContext()
  804.         context.setdefault(key, value)
  805.  
  806.     
  807.     def _start_rss(self, attrsD):
  808.         versionmap = {
  809.             '0.91': 'rss091u',
  810.             '0.92': 'rss092',
  811.             '0.93': 'rss093',
  812.             '0.94': 'rss094' }
  813.         if not self.version:
  814.             attr_version = attrsD.get('version', '')
  815.             version = versionmap.get(attr_version)
  816.             if version:
  817.                 self.version = version
  818.             elif attr_version.startswith('2.'):
  819.                 self.version = 'rss20'
  820.             else:
  821.                 self.version = 'rss'
  822.         
  823.  
  824.     
  825.     def _start_dlhottitles(self, attrsD):
  826.         self.version = 'hotrss'
  827.  
  828.     
  829.     def _start_channel(self, attrsD):
  830.         self.infeed = 1
  831.         self._cdf_common(attrsD)
  832.  
  833.     _start_feedinfo = _start_channel
  834.     
  835.     def _cdf_common(self, attrsD):
  836.         if attrsD.has_key('lastmod'):
  837.             self._start_modified({ })
  838.             self.elementstack[-1][-1] = attrsD['lastmod']
  839.             self._end_modified()
  840.         
  841.         if attrsD.has_key('href'):
  842.             self._start_link({ })
  843.             self.elementstack[-1][-1] = attrsD['href']
  844.             self._end_link()
  845.         
  846.  
  847.     
  848.     def _start_feed(self, attrsD):
  849.         self.infeed = 1
  850.         versionmap = {
  851.             '0.1': 'atom01',
  852.             '0.2': 'atom02',
  853.             '0.3': 'atom03' }
  854.         if not self.version:
  855.             attr_version = attrsD.get('version')
  856.             version = versionmap.get(attr_version)
  857.             if version:
  858.                 self.version = version
  859.             else:
  860.                 self.version = 'atom'
  861.         
  862.  
  863.     
  864.     def _end_channel(self):
  865.         self.infeed = 0
  866.  
  867.     _end_feed = _end_channel
  868.     
  869.     def _start_image(self, attrsD):
  870.         self.inimage = 1
  871.         self.push('image', 0)
  872.         context = self._getContext()
  873.         context.setdefault('image', FeedParserDict())
  874.  
  875.     
  876.     def _end_image(self):
  877.         self.pop('image')
  878.         self.inimage = 0
  879.  
  880.     
  881.     def _start_textinput(self, attrsD):
  882.         self.intextinput = 1
  883.         self.push('textinput', 0)
  884.         context = self._getContext()
  885.         context.setdefault('textinput', FeedParserDict())
  886.  
  887.     _start_textInput = _start_textinput
  888.     
  889.     def _end_textinput(self):
  890.         self.pop('textinput')
  891.         self.intextinput = 0
  892.  
  893.     _end_textInput = _end_textinput
  894.     
  895.     def _start_author(self, attrsD):
  896.         self.inauthor = 1
  897.         self.push('author', 1)
  898.  
  899.     _start_managingeditor = _start_author
  900.     _start_dc_author = _start_author
  901.     _start_dc_creator = _start_author
  902.     _start_itunes_author = _start_author
  903.     
  904.     def _end_author(self):
  905.         self.pop('author')
  906.         self.inauthor = 0
  907.         self._sync_author_detail()
  908.  
  909.     _end_managingeditor = _end_author
  910.     _end_dc_author = _end_author
  911.     _end_dc_creator = _end_author
  912.     _end_itunes_author = _end_author
  913.     
  914.     def _start_itunes_owner(self, attrsD):
  915.         self.inpublisher = 1
  916.         self.push('publisher', 0)
  917.  
  918.     
  919.     def _end_itunes_owner(self):
  920.         self.pop('publisher')
  921.         self.inpublisher = 0
  922.         self._sync_author_detail('publisher')
  923.  
  924.     
  925.     def _start_contributor(self, attrsD):
  926.         self.incontributor = 1
  927.         context = self._getContext()
  928.         context.setdefault('contributors', [])
  929.         context['contributors'].append(FeedParserDict())
  930.         self.push('contributor', 0)
  931.  
  932.     
  933.     def _end_contributor(self):
  934.         self.pop('contributor')
  935.         self.incontributor = 0
  936.  
  937.     
  938.     def _start_dc_contributor(self, attrsD):
  939.         self.incontributor = 1
  940.         context = self._getContext()
  941.         context.setdefault('contributors', [])
  942.         context['contributors'].append(FeedParserDict())
  943.         self.push('name', 0)
  944.  
  945.     
  946.     def _end_dc_contributor(self):
  947.         self._end_name()
  948.         self.incontributor = 0
  949.  
  950.     
  951.     def _start_name(self, attrsD):
  952.         self.push('name', 0)
  953.  
  954.     _start_itunes_name = _start_name
  955.     
  956.     def _end_name(self):
  957.         value = self.pop('name')
  958.         if self.inpublisher:
  959.             self._save_author('name', value, 'publisher')
  960.         elif self.inauthor:
  961.             self._save_author('name', value)
  962.         elif self.incontributor:
  963.             self._save_contributor('name', value)
  964.         elif self.intextinput:
  965.             context = self._getContext()
  966.             context['textinput']['name'] = value
  967.         
  968.  
  969.     _end_itunes_name = _end_name
  970.     
  971.     def _start_width(self, attrsD):
  972.         self.push('width', 0)
  973.  
  974.     
  975.     def _end_width(self):
  976.         value = self.pop('width')
  977.         
  978.         try:
  979.             value = int(value)
  980.         except:
  981.             value = 0
  982.  
  983.         if self.inimage:
  984.             context = self._getContext()
  985.             context['image']['width'] = value
  986.         
  987.  
  988.     
  989.     def _start_height(self, attrsD):
  990.         self.push('height', 0)
  991.  
  992.     
  993.     def _end_height(self):
  994.         value = self.pop('height')
  995.         
  996.         try:
  997.             value = int(value)
  998.         except:
  999.             value = 0
  1000.  
  1001.         if self.inimage:
  1002.             context = self._getContext()
  1003.             context['image']['height'] = value
  1004.         
  1005.  
  1006.     
  1007.     def _start_url(self, attrsD):
  1008.         self.push('href', 1)
  1009.  
  1010.     _start_homepage = _start_url
  1011.     _start_uri = _start_url
  1012.     
  1013.     def _end_url(self):
  1014.         value = self.pop('href')
  1015.         if self.inauthor:
  1016.             self._save_author('href', value)
  1017.         elif self.incontributor:
  1018.             self._save_contributor('href', value)
  1019.         elif self.inimage:
  1020.             context = self._getContext()
  1021.             context['image']['href'] = value
  1022.         elif self.intextinput:
  1023.             context = self._getContext()
  1024.             context['textinput']['link'] = value
  1025.         
  1026.  
  1027.     _end_homepage = _end_url
  1028.     _end_uri = _end_url
  1029.     
  1030.     def _start_email(self, attrsD):
  1031.         self.push('email', 0)
  1032.  
  1033.     _start_itunes_email = _start_email
  1034.     
  1035.     def _end_email(self):
  1036.         value = self.pop('email')
  1037.         if self.inpublisher:
  1038.             self._save_author('email', value, 'publisher')
  1039.         elif self.inauthor:
  1040.             self._save_author('email', value)
  1041.         elif self.incontributor:
  1042.             self._save_contributor('email', value)
  1043.         
  1044.  
  1045.     _end_itunes_email = _end_email
  1046.     
  1047.     def _getContext(self):
  1048.         if self.insource:
  1049.             context = self.sourcedata
  1050.         elif self.inentry:
  1051.             context = self.entries[-1]
  1052.         else:
  1053.             context = self.feeddata
  1054.         return context
  1055.  
  1056.     
  1057.     def _save_author(self, key, value, prefix = 'author'):
  1058.         context = self._getContext()
  1059.         context.setdefault(prefix + '_detail', FeedParserDict())
  1060.         context[prefix + '_detail'][key] = value
  1061.         self._sync_author_detail()
  1062.  
  1063.     
  1064.     def _save_contributor(self, key, value):
  1065.         context = self._getContext()
  1066.         context.setdefault('contributors', [
  1067.             FeedParserDict()])
  1068.         context['contributors'][-1][key] = value
  1069.  
  1070.     
  1071.     def _sync_author_detail(self, key = 'author'):
  1072.         context = self._getContext()
  1073.         detail = context.get('%s_detail' % key)
  1074.         if detail:
  1075.             name = detail.get('name')
  1076.             email = detail.get('email')
  1077.             if name and email:
  1078.                 context[key] = '%s (%s)' % (name, email)
  1079.             elif name:
  1080.                 context[key] = name
  1081.             elif email:
  1082.                 context[key] = email
  1083.             
  1084.         else:
  1085.             author = context.get(key)
  1086.             if not author:
  1087.                 return None
  1088.             emailmatch = re.search('(([a-zA-Z0-9\\_\\-\\.\\+]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?))', author)
  1089.             if not emailmatch:
  1090.                 return None
  1091.             email = emailmatch.group(0)
  1092.             author = author.replace(email, '')
  1093.             author = author.replace('()', '')
  1094.             author = author.strip()
  1095.             if author and author[-1] == ')':
  1096.                 author = author[:-1]
  1097.             
  1098.             author = author.strip()
  1099.             context.setdefault('%s_detail' % key, FeedParserDict())
  1100.             context['%s_detail' % key]['name'] = author
  1101.             context['%s_detail' % key]['email'] = email
  1102.  
  1103.     
  1104.     def _start_subtitle(self, attrsD):
  1105.         self.pushContent('subtitle', attrsD, 'text/plain', 1)
  1106.  
  1107.     _start_tagline = _start_subtitle
  1108.     _start_itunes_subtitle = _start_subtitle
  1109.     
  1110.     def _end_subtitle(self):
  1111.         self.popContent('subtitle')
  1112.  
  1113.     _end_tagline = _end_subtitle
  1114.     _end_itunes_subtitle = _end_subtitle
  1115.     
  1116.     def _start_rights(self, attrsD):
  1117.         self.pushContent('rights', attrsD, 'text/plain', 1)
  1118.  
  1119.     _start_dc_rights = _start_rights
  1120.     _start_copyright = _start_rights
  1121.     
  1122.     def _end_rights(self):
  1123.         self.popContent('rights')
  1124.  
  1125.     _end_dc_rights = _end_rights
  1126.     _end_copyright = _end_rights
  1127.     
  1128.     def _start_item(self, attrsD):
  1129.         self.entries.append(FeedParserDict())
  1130.         self.push('item', 0)
  1131.         self.inentry = 1
  1132.         self.guidislink = 0
  1133.         id = self._getAttribute(attrsD, 'rdf:about')
  1134.         if id:
  1135.             context = self._getContext()
  1136.             context['id'] = id
  1137.         
  1138.         self._cdf_common(attrsD)
  1139.  
  1140.     _start_entry = _start_item
  1141.     _start_product = _start_item
  1142.     
  1143.     def _end_item(self):
  1144.         self.pop('item')
  1145.         self.inentry = 0
  1146.  
  1147.     _end_entry = _end_item
  1148.     
  1149.     def _start_dc_language(self, attrsD):
  1150.         self.push('language', 1)
  1151.  
  1152.     _start_language = _start_dc_language
  1153.     
  1154.     def _end_dc_language(self):
  1155.         self.lang = self.pop('language')
  1156.  
  1157.     _end_language = _end_dc_language
  1158.     
  1159.     def _start_dc_publisher(self, attrsD):
  1160.         self.push('publisher', 1)
  1161.  
  1162.     _start_webmaster = _start_dc_publisher
  1163.     
  1164.     def _end_dc_publisher(self):
  1165.         self.pop('publisher')
  1166.         self._sync_author_detail('publisher')
  1167.  
  1168.     _end_webmaster = _end_dc_publisher
  1169.     
  1170.     def _start_published(self, attrsD):
  1171.         self.push('published', 1)
  1172.  
  1173.     _start_dcterms_issued = _start_published
  1174.     _start_issued = _start_published
  1175.     
  1176.     def _end_published(self):
  1177.         value = self.pop('published')
  1178.         self._save('published_parsed', _parse_date(value))
  1179.  
  1180.     _end_dcterms_issued = _end_published
  1181.     _end_issued = _end_published
  1182.     
  1183.     def _start_updated(self, attrsD):
  1184.         self.push('updated', 1)
  1185.  
  1186.     _start_modified = _start_updated
  1187.     _start_dcterms_modified = _start_updated
  1188.     _start_pubdate = _start_updated
  1189.     _start_dc_date = _start_updated
  1190.     
  1191.     def _end_updated(self):
  1192.         value = self.pop('updated')
  1193.         parsed_value = _parse_date(value)
  1194.         self._save('updated_parsed', parsed_value)
  1195.  
  1196.     _end_modified = _end_updated
  1197.     _end_dcterms_modified = _end_updated
  1198.     _end_pubdate = _end_updated
  1199.     _end_dc_date = _end_updated
  1200.     
  1201.     def _start_created(self, attrsD):
  1202.         self.push('created', 1)
  1203.  
  1204.     _start_dcterms_created = _start_created
  1205.     
  1206.     def _end_created(self):
  1207.         value = self.pop('created')
  1208.         self._save('created_parsed', _parse_date(value))
  1209.  
  1210.     _end_dcterms_created = _end_created
  1211.     
  1212.     def _start_expirationdate(self, attrsD):
  1213.         self.push('expired', 1)
  1214.  
  1215.     
  1216.     def _end_expirationdate(self):
  1217.         self._save('expired_parsed', _parse_date(self.pop('expired')))
  1218.  
  1219.     
  1220.     def _start_cc_license(self, attrsD):
  1221.         self.push('license', 1)
  1222.         value = self._getAttribute(attrsD, 'rdf:resource')
  1223.         if value:
  1224.             self.elementstack[-1][2].append(value)
  1225.         
  1226.         self.pop('license')
  1227.  
  1228.     
  1229.     def _start_creativecommons_license(self, attrsD):
  1230.         self.push('license', 1)
  1231.  
  1232.     
  1233.     def _end_creativecommons_license(self):
  1234.         self.pop('license')
  1235.  
  1236.     
  1237.     def _addTag(self, term, scheme, label):
  1238.         context = self._getContext()
  1239.         tags = context.setdefault('tags', [])
  1240.         if not term and not scheme and not label:
  1241.             return None
  1242.         value = FeedParserDict({
  1243.             'term': term,
  1244.             'scheme': scheme,
  1245.             'label': label })
  1246.         if value not in tags:
  1247.             tags.append(FeedParserDict({
  1248.                 'term': term,
  1249.                 'scheme': scheme,
  1250.                 'label': label }))
  1251.         
  1252.  
  1253.     
  1254.     def _start_category(self, attrsD):
  1255.         if _debug:
  1256.             sys.stderr.write('entering _start_category with %s\n' % repr(attrsD))
  1257.         
  1258.         term = attrsD.get('term')
  1259.         scheme = attrsD.get('scheme', attrsD.get('domain'))
  1260.         label = attrsD.get('label')
  1261.         self._addTag(term, scheme, label)
  1262.         self.push('category', 1)
  1263.  
  1264.     _start_dc_subject = _start_category
  1265.     _start_keywords = _start_category
  1266.     
  1267.     def _end_itunes_keywords(self):
  1268.         for term in self.pop('itunes_keywords').split():
  1269.             self._addTag(term, 'http://www.itunes.com/', None)
  1270.         
  1271.  
  1272.     
  1273.     def _start_itunes_category(self, attrsD):
  1274.         self._addTag(attrsD.get('text'), 'http://www.itunes.com/', None)
  1275.         self.push('category', 1)
  1276.  
  1277.     
  1278.     def _end_category(self):
  1279.         value = self.pop('category')
  1280.         if not value:
  1281.             return None
  1282.         context = self._getContext()
  1283.         tags = context['tags']
  1284.         if value and len(tags) and not tags[-1]['term']:
  1285.             tags[-1]['term'] = value
  1286.         else:
  1287.             self._addTag(value, None, None)
  1288.  
  1289.     _end_dc_subject = _end_category
  1290.     _end_keywords = _end_category
  1291.     _end_itunes_category = _end_category
  1292.     
  1293.     def _start_cloud(self, attrsD):
  1294.         self._getContext()['cloud'] = FeedParserDict(attrsD)
  1295.  
  1296.     
  1297.     def _start_link(self, attrsD):
  1298.         attrsD.setdefault('rel', 'alternate')
  1299.         attrsD.setdefault('type', 'text/html')
  1300.         attrsD = self._itsAnHrefDamnIt(attrsD)
  1301.         if attrsD.has_key('href'):
  1302.             attrsD['href'] = self.resolveURI(attrsD['href'])
  1303.         
  1304.         if not self.infeed and self.inentry:
  1305.             pass
  1306.         expectingText = self.insource
  1307.         context = self._getContext()
  1308.         context.setdefault('links', [])
  1309.         context['links'].append(FeedParserDict(attrsD))
  1310.         if attrsD['rel'] == 'enclosure':
  1311.             self._start_enclosure(attrsD)
  1312.         
  1313.         if attrsD.has_key('href'):
  1314.             expectingText = 0
  1315.             if attrsD.get('rel') == 'alternate' and self.mapContentType(attrsD.get('type')) in self.html_types:
  1316.                 context['link'] = attrsD['href']
  1317.             
  1318.         else:
  1319.             self.push('link', expectingText)
  1320.  
  1321.     _start_producturl = _start_link
  1322.     
  1323.     def _end_link(self):
  1324.         value = self.pop('link')
  1325.         context = self._getContext()
  1326.         if self.intextinput:
  1327.             context['textinput']['link'] = value
  1328.         
  1329.         if self.inimage:
  1330.             context['image']['link'] = value
  1331.         
  1332.  
  1333.     _end_producturl = _end_link
  1334.     
  1335.     def _start_guid(self, attrsD):
  1336.         self.guidislink = attrsD.get('ispermalink', 'true') == 'true'
  1337.         self.push('id', 1)
  1338.  
  1339.     
  1340.     def _end_guid(self):
  1341.         value = self.pop('id')
  1342.         if self.guidislink:
  1343.             pass
  1344.         self._save('guidislink', not self._getContext().has_key('link'))
  1345.         if self.guidislink:
  1346.             self._save('link', value)
  1347.         
  1348.  
  1349.     
  1350.     def _start_title(self, attrsD):
  1351.         if not self.infeed and self.inentry:
  1352.             pass
  1353.         self.pushContent('title', attrsD, 'text/plain', self.insource)
  1354.  
  1355.     
  1356.     def _start_title_low_pri(self, attrsD):
  1357.         if not self._getContext().has_key('title'):
  1358.             self._start_title(attrsD)
  1359.         
  1360.  
  1361.     _start_dc_title = _start_title_low_pri
  1362.     _start_media_title = _start_title_low_pri
  1363.     
  1364.     def _end_title(self):
  1365.         value = self.popContent('title')
  1366.         context = self._getContext()
  1367.         if self.intextinput:
  1368.             context['textinput']['title'] = value
  1369.         elif self.inimage:
  1370.             context['image']['title'] = value
  1371.         
  1372.  
  1373.     
  1374.     def _end_title_low_pri(self):
  1375.         if not self._getContext().has_key('title'):
  1376.             self._end_title()
  1377.         
  1378.  
  1379.     _end_dc_title = _end_title_low_pri
  1380.     _end_media_title = _end_title_low_pri
  1381.     
  1382.     def _start_description(self, attrsD):
  1383.         context = self._getContext()
  1384.         None(self.pushContent, 'description', attrsD, 'text/html' if context.has_key('summary') else self.insource)
  1385.  
  1386.     
  1387.     def _start_abstract(self, attrsD):
  1388.         if not self.infeed and self.inentry:
  1389.             pass
  1390.         self.pushContent('description', attrsD, 'text/plain', self.insource)
  1391.  
  1392.     
  1393.     def _end_description(self):
  1394.         if self._summaryKey == 'content':
  1395.             self._end_content()
  1396.         else:
  1397.             value = self.popContent('description')
  1398.             context = self._getContext()
  1399.             if self.intextinput:
  1400.                 context['textinput']['description'] = value
  1401.             elif self.inimage:
  1402.                 context['image']['description'] = value
  1403.             
  1404.         self._summaryKey = None
  1405.  
  1406.     _end_abstract = _end_description
  1407.     
  1408.     def _start_info(self, attrsD):
  1409.         self.pushContent('info', attrsD, 'text/plain', 1)
  1410.  
  1411.     _start_feedburner_browserfriendly = _start_info
  1412.     
  1413.     def _end_info(self):
  1414.         self.popContent('info')
  1415.  
  1416.     _end_feedburner_browserfriendly = _end_info
  1417.     
  1418.     def _start_generator(self, attrsD):
  1419.         if attrsD:
  1420.             attrsD = self._itsAnHrefDamnIt(attrsD)
  1421.             if attrsD.has_key('href'):
  1422.                 attrsD['href'] = self.resolveURI(attrsD['href'])
  1423.             
  1424.         
  1425.         self._getContext()['generator_detail'] = FeedParserDict(attrsD)
  1426.         self.push('generator', 1)
  1427.  
  1428.     
  1429.     def _end_generator(self):
  1430.         value = self.pop('generator')
  1431.         context = self._getContext()
  1432.         if context.has_key('generator_detail'):
  1433.             context['generator_detail']['name'] = value
  1434.         
  1435.  
  1436.     
  1437.     def _start_admin_generatoragent(self, attrsD):
  1438.         self.push('generator', 1)
  1439.         value = self._getAttribute(attrsD, 'rdf:resource')
  1440.         if value:
  1441.             self.elementstack[-1][2].append(value)
  1442.         
  1443.         self.pop('generator')
  1444.         self._getContext()['generator_detail'] = FeedParserDict({
  1445.             'href': value })
  1446.  
  1447.     
  1448.     def _start_admin_errorreportsto(self, attrsD):
  1449.         self.push('errorreportsto', 1)
  1450.         value = self._getAttribute(attrsD, 'rdf:resource')
  1451.         if value:
  1452.             self.elementstack[-1][2].append(value)
  1453.         
  1454.         self.pop('errorreportsto')
  1455.  
  1456.     
  1457.     def _start_summary(self, attrsD):
  1458.         context = self._getContext()
  1459.         if context.has_key('summary'):
  1460.             self._summaryKey = 'content'
  1461.             self._start_content(attrsD)
  1462.         else:
  1463.             self._summaryKey = 'summary'
  1464.             self.pushContent(self._summaryKey, attrsD, 'text/plain', 1)
  1465.  
  1466.     _start_itunes_summary = _start_summary
  1467.     
  1468.     def _end_summary(self):
  1469.         None(self.popContent if self._summaryKey == 'content' else 'summary')
  1470.         self._summaryKey = None
  1471.  
  1472.     _end_itunes_summary = _end_summary
  1473.     
  1474.     def _start_enclosure(self, attrsD):
  1475.         attrsD = self._itsAnHrefDamnIt(attrsD)
  1476.         self._getContext().setdefault('enclosures', []).append(FeedParserDict(attrsD))
  1477.         href = attrsD.get('href')
  1478.         if href:
  1479.             context = self._getContext()
  1480.             if not context.get('id'):
  1481.                 context['id'] = href
  1482.             
  1483.         
  1484.  
  1485.     
  1486.     def _start_source(self, attrsD):
  1487.         self.insource = 1
  1488.  
  1489.     
  1490.     def _end_source(self):
  1491.         self.insource = 0
  1492.         self._getContext()['source'] = copy.deepcopy(self.sourcedata)
  1493.         self.sourcedata.clear()
  1494.  
  1495.     
  1496.     def _start_content(self, attrsD):
  1497.         self.pushContent('content', attrsD, 'text/plain', 1)
  1498.         src = attrsD.get('src')
  1499.         if src:
  1500.             self.contentparams['src'] = src
  1501.         
  1502.         self.push('content', 1)
  1503.  
  1504.     
  1505.     def _start_prodlink(self, attrsD):
  1506.         self.pushContent('content', attrsD, 'text/html', 1)
  1507.  
  1508.     
  1509.     def _start_body(self, attrsD):
  1510.         self.pushContent('content', attrsD, 'application/xhtml+xml', 1)
  1511.  
  1512.     _start_xhtml_body = _start_body
  1513.     
  1514.     def _start_content_encoded(self, attrsD):
  1515.         self.pushContent('content', attrsD, 'text/html', 1)
  1516.  
  1517.     _start_fullitem = _start_content_encoded
  1518.     
  1519.     def _end_content(self):
  1520.         copyToDescription = self.mapContentType(self.contentparams.get('type')) in [
  1521.             'text/plain'] + self.html_types
  1522.         value = self.popContent('content')
  1523.         if copyToDescription:
  1524.             self._save('description', value)
  1525.         
  1526.  
  1527.     _end_body = _end_content
  1528.     _end_xhtml_body = _end_content
  1529.     _end_content_encoded = _end_content
  1530.     _end_fullitem = _end_content
  1531.     _end_prodlink = _end_content
  1532.     
  1533.     def _start_itunes_image(self, attrsD):
  1534.         self.push('itunes_image', 0)
  1535.         self._getContext()['image'] = FeedParserDict({
  1536.             'href': attrsD.get('href') })
  1537.  
  1538.     _start_itunes_link = _start_itunes_image
  1539.     
  1540.     def _end_itunes_block(self):
  1541.         value = self.pop('itunes_block', 0)
  1542.         if not value == 'yes' or 1:
  1543.             pass
  1544.         self._getContext()['itunes_block'] = 0
  1545.  
  1546.     
  1547.     def _end_itunes_explicit(self):
  1548.         value = self.pop('itunes_explicit', 0)
  1549.         if not value == 'yes' or 1:
  1550.             pass
  1551.         self._getContext()['itunes_explicit'] = 0
  1552.  
  1553.  
  1554. if _XML_AVAILABLE:
  1555.     
  1556.     class _StrictFeedParser(_FeedParserMixin, xml.sax.handler.ContentHandler):
  1557.         
  1558.         def __init__(self, baseuri, baselang, encoding):
  1559.             if _debug:
  1560.                 sys.stderr.write('trying StrictFeedParser\n')
  1561.             
  1562.             xml.sax.handler.ContentHandler.__init__(self)
  1563.             _FeedParserMixin.__init__(self, baseuri, baselang, encoding)
  1564.             self.bozo = 0
  1565.             self.exc = None
  1566.  
  1567.         
  1568.         def startPrefixMapping(self, prefix, uri):
  1569.             self.trackNamespace(prefix, uri)
  1570.  
  1571.         
  1572.         def startElementNS(self, name, qname, attrs):
  1573.             (namespace, localname) = name
  1574.             if not namespace:
  1575.                 pass
  1576.             lowernamespace = str('').lower()
  1577.             if lowernamespace.find('backend.userland.com/rss') != -1:
  1578.                 namespace = 'http://backend.userland.com/rss'
  1579.                 lowernamespace = namespace
  1580.             
  1581.             if qname and qname.find(':') > 0:
  1582.                 givenprefix = qname.split(':')[0]
  1583.             else:
  1584.                 givenprefix = None
  1585.             prefix = self._matchnamespaces.get(lowernamespace, givenprefix)
  1586.             if givenprefix:
  1587.                 if (prefix == None or prefix == '' or lowernamespace == '') and not self.namespacesInUse.has_key(givenprefix):
  1588.                     raise UndeclaredNamespace, "'%s' is not associated with a namespace" % givenprefix
  1589.             not self.namespacesInUse.has_key(givenprefix)
  1590.             if prefix:
  1591.                 localname = prefix + ':' + localname
  1592.             
  1593.             localname = str(localname).lower()
  1594.             if _debug:
  1595.                 sys.stderr.write('startElementNS: qname = %s, namespace = %s, givenprefix = %s, prefix = %s, attrs = %s, localname = %s\n' % (qname, namespace, givenprefix, prefix, attrs.items(), localname))
  1596.             
  1597.             attrsD = { }
  1598.             for namespace, attrlocalname in attrs._attrs.items():
  1599.                 attrvalue = None
  1600.                 if not namespace:
  1601.                     pass
  1602.                 lowernamespace = ''.lower()
  1603.                 prefix = self._matchnamespaces.get(lowernamespace, '')
  1604.                 if prefix:
  1605.                     attrlocalname = prefix + ':' + attrlocalname
  1606.                 
  1607.                 attrsD[str(attrlocalname).lower()] = attrvalue
  1608.             
  1609.             for qname in attrs.getQNames():
  1610.                 attrsD[str(qname).lower()] = attrs.getValueByQName(qname)
  1611.             
  1612.             self.unknown_starttag(localname, attrsD.items())
  1613.  
  1614.         
  1615.         def characters(self, text):
  1616.             self.handle_data(text)
  1617.  
  1618.         
  1619.         def endElementNS(self, name, qname):
  1620.             (namespace, localname) = name
  1621.             if not namespace:
  1622.                 pass
  1623.             lowernamespace = str('').lower()
  1624.             if qname and qname.find(':') > 0:
  1625.                 givenprefix = qname.split(':')[0]
  1626.             else:
  1627.                 givenprefix = ''
  1628.             prefix = self._matchnamespaces.get(lowernamespace, givenprefix)
  1629.             if prefix:
  1630.                 localname = prefix + ':' + localname
  1631.             
  1632.             localname = str(localname).lower()
  1633.             self.unknown_endtag(localname)
  1634.  
  1635.         
  1636.         def error(self, exc):
  1637.             self.bozo = 1
  1638.             self.exc = exc
  1639.  
  1640.         
  1641.         def fatalError(self, exc):
  1642.             self.error(exc)
  1643.             raise exc
  1644.  
  1645.  
  1646.  
  1647.  
  1648. class _BaseHTMLProcessor(sgmllib.SGMLParser):
  1649.     elements_no_end_tag = [
  1650.         'area',
  1651.         'base',
  1652.         'basefont',
  1653.         'br',
  1654.         'col',
  1655.         'frame',
  1656.         'hr',
  1657.         'img',
  1658.         'input',
  1659.         'isindex',
  1660.         'link',
  1661.         'meta',
  1662.         'param']
  1663.     
  1664.     def __init__(self, encoding):
  1665.         self.encoding = encoding
  1666.         if _debug:
  1667.             sys.stderr.write('entering BaseHTMLProcessor, encoding=%s\n' % self.encoding)
  1668.         
  1669.         sgmllib.SGMLParser.__init__(self)
  1670.  
  1671.     
  1672.     def reset(self):
  1673.         self.pieces = []
  1674.         sgmllib.SGMLParser.reset(self)
  1675.  
  1676.     
  1677.     def _shorttag_replace(self, match):
  1678.         tag = match.group(1)
  1679.         if tag in self.elements_no_end_tag:
  1680.             return '<' + tag + ' />'
  1681.         return '<' + tag + '></' + tag + '>'
  1682.  
  1683.     
  1684.     def feed(self, data):
  1685.         data = re.compile('<!((?!DOCTYPE|--|\\[))', re.IGNORECASE).sub('<!\\1', data)
  1686.         data = re.sub('<([^<\\s]+?)\\s*/>', self._shorttag_replace, data)
  1687.         data = data.replace(''', "'")
  1688.         data = data.replace('"', '"')
  1689.         if self.encoding and type(data) == type(u''):
  1690.             data = data.encode(self.encoding)
  1691.         
  1692.         sgmllib.SGMLParser.feed(self, data)
  1693.  
  1694.     
  1695.     def normalize_attrs(self, attrs):
  1696.         attrs = [ (k.lower(), v) for k, v in attrs ]
  1697.         attrs = [ (k, v) for k, v in attrs ]
  1698.         return attrs
  1699.  
  1700.     
  1701.     def unknown_starttag(self, tag, attrs):
  1702.         if _debug:
  1703.             sys.stderr.write('_BaseHTMLProcessor, unknown_starttag, tag=%s\n' % tag)
  1704.         
  1705.         uattrs = []
  1706.         for key, value in attrs:
  1707.             if type(value) != type(u''):
  1708.                 value = unicode(value, self.encoding, errors = 'replace')
  1709.             
  1710.             uattrs.append((unicode(key, self.encoding), value))
  1711.         
  1712.         strattrs = []([ u' %s="%s"' % (key, value) for key, value in uattrs ]).encode(self.encoding)
  1713.  
  1714.     
  1715.     def unknown_endtag(self, tag):
  1716.         if tag not in self.elements_no_end_tag:
  1717.             self.pieces.append('</%(tag)s>' % locals())
  1718.         
  1719.  
  1720.     
  1721.     def handle_charref(self, ref):
  1722.         self.pieces.append('&#%(ref)s;' % locals())
  1723.  
  1724.     
  1725.     def handle_entityref(self, ref):
  1726.         self.pieces.append('&%(ref)s;' % locals())
  1727.  
  1728.     
  1729.     def handle_data(self, text):
  1730.         if _debug:
  1731.             sys.stderr.write('_BaseHTMLProcessor, handle_text, text=%s\n' % text)
  1732.         
  1733.         self.pieces.append(text)
  1734.  
  1735.     
  1736.     def handle_comment(self, text):
  1737.         self.pieces.append('<!--%(text)s-->' % locals())
  1738.  
  1739.     
  1740.     def handle_pi(self, text):
  1741.         self.pieces.append('<?%(text)s>' % locals())
  1742.  
  1743.     
  1744.     def handle_decl(self, text):
  1745.         self.pieces.append('<!%(text)s>' % locals())
  1746.  
  1747.     _new_declname_match = re.compile('[a-zA-Z][-_.a-zA-Z0-9:]*\\s*').match
  1748.     
  1749.     def _scan_name(self, i, declstartpos):
  1750.         rawdata = self.rawdata
  1751.         n = len(rawdata)
  1752.         if i == n:
  1753.             return (None, -1)
  1754.         m = self._new_declname_match(rawdata, i)
  1755.         if m:
  1756.             s = m.group()
  1757.             name = s.strip()
  1758.             if i + len(s) == n:
  1759.                 return (None, -1)
  1760.             return (name.lower(), m.end())
  1761.         self.handle_data(rawdata)
  1762.         return (None, -1)
  1763.  
  1764.     
  1765.     def output(self):
  1766.         '''Return processed HTML as a single string'''
  1767.         return []([ str(p) for p in self.pieces ])
  1768.  
  1769.  
  1770.  
  1771. class _LooseFeedParser(_FeedParserMixin, _BaseHTMLProcessor):
  1772.     
  1773.     def __init__(self, baseuri, baselang, encoding):
  1774.         sgmllib.SGMLParser.__init__(self)
  1775.         _FeedParserMixin.__init__(self, baseuri, baselang, encoding)
  1776.  
  1777.     
  1778.     def decodeEntities(self, element, data):
  1779.         data = data.replace('<', '<')
  1780.         data = data.replace('<', '<')
  1781.         data = data.replace('>', '>')
  1782.         data = data.replace('>', '>')
  1783.         data = data.replace('&', '&')
  1784.         data = data.replace('&', '&')
  1785.         data = data.replace('"', '"')
  1786.         data = data.replace('"', '"')
  1787.         data = data.replace(''', ''')
  1788.         data = data.replace(''', ''')
  1789.         if self.contentparams.has_key('type') and not self.contentparams.get('type', 'xml').endswith('xml'):
  1790.             data = data.replace('<', '<')
  1791.             data = data.replace('>', '>')
  1792.             data = data.replace('&', '&')
  1793.             data = data.replace('"', '"')
  1794.             data = data.replace(''', "'")
  1795.         
  1796.         return data
  1797.  
  1798.  
  1799.  
  1800. class _RelativeURIResolver(_BaseHTMLProcessor):
  1801.     relative_uris = [
  1802.         ('a', 'href'),
  1803.         ('applet', 'codebase'),
  1804.         ('area', 'href'),
  1805.         ('blockquote', 'cite'),
  1806.         ('body', 'background'),
  1807.         ('del', 'cite'),
  1808.         ('form', 'action'),
  1809.         ('frame', 'longdesc'),
  1810.         ('frame', 'src'),
  1811.         ('iframe', 'longdesc'),
  1812.         ('iframe', 'src'),
  1813.         ('head', 'profile'),
  1814.         ('img', 'longdesc'),
  1815.         ('img', 'src'),
  1816.         ('img', 'usemap'),
  1817.         ('input', 'src'),
  1818.         ('input', 'usemap'),
  1819.         ('ins', 'cite'),
  1820.         ('link', 'href'),
  1821.         ('object', 'classid'),
  1822.         ('object', 'codebase'),
  1823.         ('object', 'data'),
  1824.         ('object', 'usemap'),
  1825.         ('q', 'cite'),
  1826.         ('script', 'src')]
  1827.     
  1828.     def __init__(self, baseuri, encoding):
  1829.         _BaseHTMLProcessor.__init__(self, encoding)
  1830.         self.baseuri = baseuri
  1831.  
  1832.     
  1833.     def resolveURI(self, uri):
  1834.         return _urljoin(self.baseuri, uri)
  1835.  
  1836.     
  1837.     def unknown_starttag(self, tag, attrs):
  1838.         attrs = self.normalize_attrs(attrs)
  1839.         attrs = [ (key, value) for key, value in attrs ]
  1840.         _BaseHTMLProcessor.unknown_starttag(self, tag, attrs)
  1841.  
  1842.  
  1843.  
  1844. def _resolveRelativeURIs(htmlSource, baseURI, encoding):
  1845.     if _debug:
  1846.         sys.stderr.write('entering _resolveRelativeURIs\n')
  1847.     
  1848.     p = _RelativeURIResolver(baseURI, encoding)
  1849.     p.feed(htmlSource)
  1850.     return p.output()
  1851.  
  1852.  
  1853. class _HTMLSanitizer(_BaseHTMLProcessor):
  1854.     acceptable_elements = [
  1855.         'a',
  1856.         'abbr',
  1857.         'acronym',
  1858.         'address',
  1859.         'area',
  1860.         'b',
  1861.         'big',
  1862.         'blockquote',
  1863.         'br',
  1864.         'button',
  1865.         'caption',
  1866.         'center',
  1867.         'cite',
  1868.         'code',
  1869.         'col',
  1870.         'colgroup',
  1871.         'dd',
  1872.         'del',
  1873.         'dfn',
  1874.         'dir',
  1875.         'div',
  1876.         'dl',
  1877.         'dt',
  1878.         'em',
  1879.         'fieldset',
  1880.         'font',
  1881.         'form',
  1882.         'h1',
  1883.         'h2',
  1884.         'h3',
  1885.         'h4',
  1886.         'h5',
  1887.         'h6',
  1888.         'hr',
  1889.         'i',
  1890.         'img',
  1891.         'input',
  1892.         'ins',
  1893.         'kbd',
  1894.         'label',
  1895.         'legend',
  1896.         'li',
  1897.         'map',
  1898.         'menu',
  1899.         'ol',
  1900.         'optgroup',
  1901.         'option',
  1902.         'p',
  1903.         'pre',
  1904.         'q',
  1905.         's',
  1906.         'samp',
  1907.         'select',
  1908.         'small',
  1909.         'span',
  1910.         'strike',
  1911.         'strong',
  1912.         'sub',
  1913.         'sup',
  1914.         'table',
  1915.         'tbody',
  1916.         'td',
  1917.         'textarea',
  1918.         'tfoot',
  1919.         'th',
  1920.         'thead',
  1921.         'tr',
  1922.         'tt',
  1923.         'u',
  1924.         'ul',
  1925.         'var']
  1926.     acceptable_attributes = [
  1927.         'abbr',
  1928.         'accept',
  1929.         'accept-charset',
  1930.         'accesskey',
  1931.         'action',
  1932.         'align',
  1933.         'alt',
  1934.         'axis',
  1935.         'border',
  1936.         'cellpadding',
  1937.         'cellspacing',
  1938.         'char',
  1939.         'charoff',
  1940.         'charset',
  1941.         'checked',
  1942.         'cite',
  1943.         'class',
  1944.         'clear',
  1945.         'cols',
  1946.         'colspan',
  1947.         'color',
  1948.         'compact',
  1949.         'coords',
  1950.         'datetime',
  1951.         'dir',
  1952.         'disabled',
  1953.         'enctype',
  1954.         'for',
  1955.         'frame',
  1956.         'headers',
  1957.         'height',
  1958.         'href',
  1959.         'hreflang',
  1960.         'hspace',
  1961.         'id',
  1962.         'ismap',
  1963.         'label',
  1964.         'lang',
  1965.         'longdesc',
  1966.         'maxlength',
  1967.         'media',
  1968.         'method',
  1969.         'multiple',
  1970.         'name',
  1971.         'nohref',
  1972.         'noshade',
  1973.         'nowrap',
  1974.         'prompt',
  1975.         'readonly',
  1976.         'rel',
  1977.         'rev',
  1978.         'rows',
  1979.         'rowspan',
  1980.         'rules',
  1981.         'scope',
  1982.         'selected',
  1983.         'shape',
  1984.         'size',
  1985.         'span',
  1986.         'src',
  1987.         'start',
  1988.         'summary',
  1989.         'tabindex',
  1990.         'target',
  1991.         'title',
  1992.         'type',
  1993.         'usemap',
  1994.         'valign',
  1995.         'value',
  1996.         'vspace',
  1997.         'width']
  1998.     unacceptable_elements_with_end_tag = [
  1999.         'script',
  2000.         'applet']
  2001.     
  2002.     def reset(self):
  2003.         _BaseHTMLProcessor.reset(self)
  2004.         self.unacceptablestack = 0
  2005.  
  2006.     
  2007.     def unknown_starttag(self, tag, attrs):
  2008.         if tag not in self.acceptable_elements:
  2009.             if tag in self.unacceptable_elements_with_end_tag:
  2010.                 self.unacceptablestack += 1
  2011.             
  2012.             return None
  2013.         attrs = self.normalize_attrs(attrs)
  2014.         attrs = _[1]
  2015.         _BaseHTMLProcessor.unknown_starttag(self, tag, attrs)
  2016.  
  2017.     
  2018.     def unknown_endtag(self, tag):
  2019.         if tag not in self.acceptable_elements:
  2020.             if tag in self.unacceptable_elements_with_end_tag:
  2021.                 self.unacceptablestack -= 1
  2022.             
  2023.             return None
  2024.         _BaseHTMLProcessor.unknown_endtag(self, tag)
  2025.  
  2026.     
  2027.     def handle_pi(self, text):
  2028.         pass
  2029.  
  2030.     
  2031.     def handle_decl(self, text):
  2032.         pass
  2033.  
  2034.     
  2035.     def handle_data(self, text):
  2036.         if not self.unacceptablestack:
  2037.             _BaseHTMLProcessor.handle_data(self, text)
  2038.         
  2039.  
  2040.  
  2041.  
  2042. def _sanitizeHTML(htmlSource, encoding):
  2043.     p = _HTMLSanitizer(encoding)
  2044.     p.feed(htmlSource)
  2045.     data = p.output()
  2046.     if TIDY_MARKUP:
  2047.         _tidy = None
  2048.         for tidy_interface in PREFERRED_TIDY_INTERFACES:
  2049.             
  2050.             try:
  2051.                 if tidy_interface == 'uTidy':
  2052.                     _utidy = parseString
  2053.                     import tidy
  2054.                     
  2055.                     def _tidy(data, **kwargs):
  2056.                         return str(_utidy(data, **kwargs))
  2057.  
  2058.                     break
  2059.                 elif tidy_interface == 'mxTidy':
  2060.                     _mxtidy = Tidy
  2061.                     import mx.Tidy
  2062.                     
  2063.                     def _tidy(data, **kwargs):
  2064.                         (nerrors, nwarnings, data, errordata) = _mxtidy.tidy(data, **kwargs)
  2065.                         return data
  2066.  
  2067.                     break
  2068.             continue
  2069.             continue
  2070.  
  2071.         
  2072.         if _tidy:
  2073.             utf8 = type(data) == type(u'')
  2074.             if utf8:
  2075.                 data = data.encode('utf-8')
  2076.             
  2077.             data = _tidy(data, output_xhtml = 1, numeric_entities = 1, wrap = 0, char_encoding = 'utf8')
  2078.             if utf8:
  2079.                 data = unicode(data, 'utf-8')
  2080.             
  2081.             if data.count('<body'):
  2082.                 data = data.split('<body', 1)[1]
  2083.                 if data.count('>'):
  2084.                     data = data.split('>', 1)[1]
  2085.                 
  2086.             
  2087.             if data.count('</body'):
  2088.                 data = data.split('</body', 1)[0]
  2089.             
  2090.         
  2091.     
  2092.     data = data.strip().replace('\r\n', '\n')
  2093.     return data
  2094.  
  2095.  
  2096. class _FeedURLHandler(urllib2.HTTPDigestAuthHandler, urllib2.HTTPRedirectHandler, urllib2.HTTPDefaultErrorHandler):
  2097.     
  2098.     def http_error_default(self, req, fp, code, msg, headers):
  2099.         if code / 100 == 3 and code != 304:
  2100.             return self.http_error_302(req, fp, code, msg, headers)
  2101.         infourl = urllib.addinfourl(fp, headers, req.get_full_url())
  2102.         infourl.status = code
  2103.         return infourl
  2104.  
  2105.     
  2106.     def http_error_302(self, req, fp, code, msg, headers):
  2107.         if headers.dict.has_key('location'):
  2108.             infourl = urllib2.HTTPRedirectHandler.http_error_302(self, req, fp, code, msg, headers)
  2109.         else:
  2110.             infourl = urllib.addinfourl(fp, headers, req.get_full_url())
  2111.         if not hasattr(infourl, 'status'):
  2112.             infourl.status = code
  2113.         
  2114.         return infourl
  2115.  
  2116.     
  2117.     def http_error_301(self, req, fp, code, msg, headers):
  2118.         if headers.dict.has_key('location'):
  2119.             infourl = urllib2.HTTPRedirectHandler.http_error_301(self, req, fp, code, msg, headers)
  2120.         else:
  2121.             infourl = urllib.addinfourl(fp, headers, req.get_full_url())
  2122.         if not hasattr(infourl, 'status'):
  2123.             infourl.status = code
  2124.         
  2125.         return infourl
  2126.  
  2127.     http_error_300 = http_error_302
  2128.     http_error_303 = http_error_302
  2129.     http_error_307 = http_error_302
  2130.     
  2131.     def http_error_401(self, req, fp, code, msg, headers):
  2132.         host = urlparse.urlparse(req.get_full_url())[1]
  2133.         
  2134.         try:
  2135.             if not sys.version.split()[0] >= '2.3.3':
  2136.                 raise AssertionError
  2137.             if not base64 != None:
  2138.                 raise AssertionError
  2139.             (user, passw) = base64.decodestring(req.headers['Authorization'].split(' ')[1]).split(':')
  2140.             realm = re.findall('realm="([^"]*)"', headers['WWW-Authenticate'])[0]
  2141.             self.add_password(realm, host, user, passw)
  2142.             retry = self.http_error_auth_reqed('www-authenticate', host, req, headers)
  2143.             self.reset_retry_count()
  2144.             return retry
  2145.         except:
  2146.             return self.http_error_default(req, fp, code, msg, headers)
  2147.  
  2148.  
  2149.  
  2150.  
  2151. def _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, handlers):
  2152.     """URL, filename, or string --> stream
  2153.  
  2154.     This function lets you define parsers that take any input source
  2155.     (URL, pathname to local or network file, or actual data as a string)
  2156.     and deal with it in a uniform manner.  Returned object is guaranteed
  2157.     to have all the basic stdio read methods (read, readline, readlines).
  2158.     Just .close() the object when you're done with it.
  2159.  
  2160.     If the etag argument is supplied, it will be used as the value of an
  2161.     If-None-Match request header.
  2162.  
  2163.     If the modified argument is supplied, it must be a tuple of 9 integers
  2164.     as returned by gmtime() in the standard Python time module. This MUST
  2165.     be in GMT (Greenwich Mean Time). The formatted date/time will be used
  2166.     as the value of an If-Modified-Since request header.
  2167.  
  2168.     If the agent argument is supplied, it will be used as the value of a
  2169.     User-Agent request header.
  2170.  
  2171.     If the referrer argument is supplied, it will be used as the value of a
  2172.     Referer[sic] request header.
  2173.  
  2174.     If handlers is supplied, it is a list of handlers used to build a
  2175.     urllib2 opener.
  2176.     """
  2177.     if hasattr(url_file_stream_or_string, 'read'):
  2178.         return url_file_stream_or_string
  2179.     if url_file_stream_or_string == '-':
  2180.         return sys.stdin
  2181.     
  2182.     try:
  2183.         return open(url_file_stream_or_string)
  2184.     except:
  2185.         hasattr(url_file_stream_or_string, 'read') if urlparse.urlparse(url_file_stream_or_string)[0] in ('http', 'https', 'ftp') else url_file_stream_or_string == '-'
  2186.  
  2187.     return _StringIO(str(url_file_stream_or_string))
  2188.  
  2189. _date_handlers = []
  2190.  
  2191. def registerDateHandler(func):
  2192.     '''Register a date handler function (takes string, returns 9-tuple date in GMT)'''
  2193.     _date_handlers.insert(0, func)
  2194.  
  2195. _iso8601_tmpl = [
  2196.     'YYYY-?MM-?DD',
  2197.     'YYYY-MM',
  2198.     'YYYY-?OOO',
  2199.     'YY-?MM-?DD',
  2200.     'YY-?OOO',
  2201.     'YYYY',
  2202.     '-YY-?MM',
  2203.     '-OOO',
  2204.     '-YY',
  2205.     '--MM-?DD',
  2206.     '--MM',
  2207.     '---DD',
  2208.     'CC',
  2209.     '']
  2210. _iso8601_re = [ tmpl.replace('YYYY', '(?P<year>\\d{4})').replace('YY', '(?P<year>\\d\\d)').replace('MM', '(?P<month>[01]\\d)').replace('DD', '(?P<day>[0123]\\d)').replace('OOO', '(?P<ordinal>[0123]\\d\\d)').replace('CC', '(?P<century>\\d\\d$)') + '(T?(?P<hour>\\d{2}):(?P<minute>\\d{2})' + '(:(?P<second>\\d{2}))?' + '(?P<tz>[+-](?P<tzhour>\\d{2})(:(?P<tzmin>\\d{2}))?|Z)?)?' for tmpl in _iso8601_tmpl ]
  2211. del tmpl
  2212. _iso8601_matches = [ re.compile(regex).match for regex in _iso8601_re ]
  2213. del regex
  2214.  
  2215. def _parse_date_iso8601(dateString):
  2216.     '''Parse a variety of ISO-8601-compatible formats like 20040105'''
  2217.     m = None
  2218.     for _iso8601_match in _iso8601_matches:
  2219.         m = _iso8601_match(dateString)
  2220.         if m:
  2221.             break
  2222.             continue
  2223.     
  2224.     if not m:
  2225.         return None
  2226.     if m.span() == (0, 0):
  2227.         return None
  2228.     params = m.groupdict()
  2229.     ordinal = params.get('ordinal', 0)
  2230.     year = params.get('year', '--')
  2231.     if not year or year == '--':
  2232.         year = time.gmtime()[0]
  2233.     elif len(year) == 2:
  2234.         year = 100 * int(time.gmtime()[0] / 100) + int(year)
  2235.     else:
  2236.         year = int(year)
  2237.     month = params.get('month', '-')
  2238.     if not month or month == '-':
  2239.         if ordinal:
  2240.             month = 1
  2241.         else:
  2242.             month = time.gmtime()[1]
  2243.     
  2244.     month = int(month)
  2245.     day = params.get('day', 0)
  2246.     if not day:
  2247.         if ordinal:
  2248.             day = ordinal
  2249.         elif params.get('century', 0) and params.get('year', 0) or params.get('month', 0):
  2250.             day = 1
  2251.         else:
  2252.             day = time.gmtime()[2]
  2253.     else:
  2254.         day = int(day)
  2255.     if 'century' in params.keys():
  2256.         year = (int(params['century']) - 1) * 100 + 1
  2257.     
  2258.     for field in [
  2259.         'hour',
  2260.         'minute',
  2261.         'second',
  2262.         'tzhour',
  2263.         'tzmin']:
  2264.         if not params.get(field, None):
  2265.             params[field] = 0
  2266.             continue
  2267.     
  2268.     hour = int(params.get('hour', 0))
  2269.     minute = int(params.get('minute', 0))
  2270.     second = int(params.get('second', 0))
  2271.     weekday = 0
  2272.     daylight_savings_flag = 0
  2273.     tm = [
  2274.         year,
  2275.         month,
  2276.         day,
  2277.         hour,
  2278.         minute,
  2279.         second,
  2280.         weekday,
  2281.         ordinal,
  2282.         daylight_savings_flag]
  2283.     tz = params.get('tz')
  2284.     if tz and tz != 'Z':
  2285.         if tz[0] == '-':
  2286.             tm[3] += int(params.get('tzhour', 0))
  2287.             tm[4] += int(params.get('tzmin', 0))
  2288.         elif tz[0] == '+':
  2289.             tm[3] -= int(params.get('tzhour', 0))
  2290.             tm[4] -= int(params.get('tzmin', 0))
  2291.         else:
  2292.             return None
  2293.     tz[0] == '-'
  2294.     return time.localtime(time.mktime(tm))
  2295.  
  2296. registerDateHandler(_parse_date_iso8601)
  2297. _korean_year = u'\xeb\x85\x84'
  2298. _korean_month = u'\xec\x9b\x94'
  2299. _korean_day = u'\xec\x9d\xbc'
  2300. _korean_am = u'\xec\x98\xa4\xec\xa0\x84'
  2301. _korean_pm = u'\xec\x98\xa4\xed\x9b\x84'
  2302. _korean_onblog_date_re = re.compile('(\\d{4})%s\\s+(\\d{2})%s\\s+(\\d{2})%s\\s+(\\d{2}):(\\d{2}):(\\d{2})' % (_korean_year, _korean_month, _korean_day))
  2303. _korean_nate_date_re = re.compile(u'(\\d{4})-(\\d{2})-(\\d{2})\\s+(%s|%s)\\s+(\\d{,2}):(\\d{,2}):(\\d{,2})' % (_korean_am, _korean_pm))
  2304.  
  2305. def _parse_date_onblog(dateString):
  2306.     '''Parse a string according to the OnBlog 8-bit date format'''
  2307.     m = _korean_onblog_date_re.match(dateString)
  2308.     if not m:
  2309.         return None
  2310.     w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s:%(second)s%(zonediff)s' % {
  2311.         'year': m.group(1),
  2312.         'month': m.group(2),
  2313.         'day': m.group(3),
  2314.         'hour': m.group(4),
  2315.         'minute': m.group(5),
  2316.         'second': m.group(6),
  2317.         'zonediff': '+09:00' }
  2318.     if _debug:
  2319.         sys.stderr.write('OnBlog date parsed as: %s\n' % w3dtfdate)
  2320.     
  2321.     return _parse_date_w3dtf(w3dtfdate)
  2322.  
  2323. registerDateHandler(_parse_date_onblog)
  2324.  
  2325. def _parse_date_nate(dateString):
  2326.     '''Parse a string according to the Nate 8-bit date format'''
  2327.     m = _korean_nate_date_re.match(dateString)
  2328.     if not m:
  2329.         return None
  2330.     hour = int(m.group(5))
  2331.     ampm = m.group(4)
  2332.     if ampm == _korean_pm:
  2333.         hour += 12
  2334.     
  2335.     hour = str(hour)
  2336.     if len(hour) == 1:
  2337.         hour = '0' + hour
  2338.     
  2339.     w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s:%(second)s%(zonediff)s' % {
  2340.         'year': m.group(1),
  2341.         'month': m.group(2),
  2342.         'day': m.group(3),
  2343.         'hour': hour,
  2344.         'minute': m.group(6),
  2345.         'second': m.group(7),
  2346.         'zonediff': '+09:00' }
  2347.     if _debug:
  2348.         sys.stderr.write('Nate date parsed as: %s\n' % w3dtfdate)
  2349.     
  2350.     return _parse_date_w3dtf(w3dtfdate)
  2351.  
  2352. registerDateHandler(_parse_date_nate)
  2353. _mssql_date_re = re.compile('(\\d{4})-(\\d{2})-(\\d{2})\\s+(\\d{2}):(\\d{2}):(\\d{2})(\\.\\d+)?')
  2354.  
  2355. def _parse_date_mssql(dateString):
  2356.     '''Parse a string according to the MS SQL date format'''
  2357.     m = _mssql_date_re.match(dateString)
  2358.     if not m:
  2359.         return None
  2360.     w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s:%(second)s%(zonediff)s' % {
  2361.         'year': m.group(1),
  2362.         'month': m.group(2),
  2363.         'day': m.group(3),
  2364.         'hour': m.group(4),
  2365.         'minute': m.group(5),
  2366.         'second': m.group(6),
  2367.         'zonediff': '+09:00' }
  2368.     if _debug:
  2369.         sys.stderr.write('MS SQL date parsed as: %s\n' % w3dtfdate)
  2370.     
  2371.     return _parse_date_w3dtf(w3dtfdate)
  2372.  
  2373. registerDateHandler(_parse_date_mssql)
  2374. _greek_months = {
  2375.     u'\xce\x99\xce\xb1\xce\xbd': u'Jan',
  2376.     u'\xce\xa6\xce\xb5\xce\xb2': u'Feb',
  2377.     u'\xce\x9c\xce\xac\xcf\x8e': u'Mar',
  2378.     u'\xce\x9c\xce\xb1\xcf\x8e': u'Mar',
  2379.     u'\xce\x91\xcf\x80\xcf\x81': u'Apr',
  2380.     u'\xce\x9c\xce\xac\xce\xb9': u'May',
  2381.     u'\xce\x9c\xce\xb1\xcf\x8a': u'May',
  2382.     u'\xce\x9c\xce\xb1\xce\xb9': u'May',
  2383.     u'\xce\x99\xce\xbf\xcf\x8d\xce\xbd': u'Jun',
  2384.     u'\xce\x99\xce\xbf\xce\xbd': u'Jun',
  2385.     u'\xce\x99\xce\xbf\xcf\x8d\xce\xbb': u'Jul',
  2386.     u'\xce\x99\xce\xbf\xce\xbb': u'Jul',
  2387.     u'\xce\x91\xcf\x8d\xce\xb3': u'Aug',
  2388.     u'\xce\x91\xcf\x85\xce\xb3': u'Aug',
  2389.     u'\xce\xa3\xce\xb5\xcf\x80': u'Sep',
  2390.     u'\xce\x9f\xce\xba\xcf\x84': u'Oct',
  2391.     u'\xce\x9d\xce\xbf\xce\xad': u'Nov',
  2392.     u'\xce\x9d\xce\xbf\xce\xb5': u'Nov',
  2393.     u'\xce\x94\xce\xb5\xce\xba': u'Dec' }
  2394. _greek_wdays = {
  2395.     u'\xce\x9a\xcf\x85\xcf\x81': u'Sun',
  2396.     u'\xce\x94\xce\xb5\xcf\x85': u'Mon',
  2397.     u'\xce\xa4\xcf\x81\xce\xb9': u'Tue',
  2398.     u'\xce\xa4\xce\xb5\xcf\x84': u'Wed',
  2399.     u'\xce\xa0\xce\xb5\xce\xbc': u'Thu',
  2400.     u'\xce\xa0\xce\xb1\xcf\x81': u'Fri',
  2401.     u'\xce\xa3\xce\xb1\xce\xb2': u'Sat' }
  2402. _greek_date_format_re = re.compile(u'([^,]+),\\s+(\\d{2})\\s+([^\\s]+)\\s+(\\d{4})\\s+(\\d{2}):(\\d{2}):(\\d{2})\\s+([^\\s]+)')
  2403.  
  2404. def _parse_date_greek(dateString):
  2405.     '''Parse a string according to a Greek 8-bit date format.'''
  2406.     m = _greek_date_format_re.match(dateString)
  2407.     if not m:
  2408.         return None
  2409.     
  2410.     try:
  2411.         wday = _greek_wdays[m.group(1)]
  2412.         month = _greek_months[m.group(3)]
  2413.     except:
  2414.         m
  2415.         return None
  2416.  
  2417.     rfc822date = '%(wday)s, %(day)s %(month)s %(year)s %(hour)s:%(minute)s:%(second)s %(zonediff)s' % {
  2418.         'wday': wday,
  2419.         'day': m.group(2),
  2420.         'month': month,
  2421.         'year': m.group(4),
  2422.         'hour': m.group(5),
  2423.         'minute': m.group(6),
  2424.         'second': m.group(7),
  2425.         'zonediff': m.group(8) }
  2426.     if _debug:
  2427.         sys.stderr.write('Greek date parsed as: %s\n' % rfc822date)
  2428.     
  2429.     return _parse_date_rfc822(rfc822date)
  2430.  
  2431. registerDateHandler(_parse_date_greek)
  2432. _hungarian_months = {
  2433.     u'janu\xc3\xa1r': u'01',
  2434.     u'febru\xc3\xa1ri': u'02',
  2435.     u'm\xc3\xa1rcius': u'03',
  2436.     u'\xc3\xa1prilis': u'04',
  2437.     u'm\xc3\xa1ujus': u'05',
  2438.     u'j\xc3\xbanius': u'06',
  2439.     u'j\xc3\xbalius': u'07',
  2440.     u'augusztus': u'08',
  2441.     u'szeptember': u'09',
  2442.     u'okt\xc3\xb3ber': u'10',
  2443.     u'november': u'11',
  2444.     u'december': u'12' }
  2445. _hungarian_date_format_re = re.compile(u'(\\d{4})-([^-]+)-(\\d{,2})T(\\d{,2}):(\\d{2})((\\+|-)(\\d{,2}:\\d{2}))')
  2446.  
  2447. def _parse_date_hungarian(dateString):
  2448.     '''Parse a string according to a Hungarian 8-bit date format.'''
  2449.     m = _hungarian_date_format_re.match(dateString)
  2450.     if not m:
  2451.         return None
  2452.     
  2453.     try:
  2454.         month = _hungarian_months[m.group(2)]
  2455.         day = m.group(3)
  2456.         if len(day) == 1:
  2457.             day = '0' + day
  2458.         
  2459.         hour = m.group(4)
  2460.         if len(hour) == 1:
  2461.             hour = '0' + hour
  2462.     except:
  2463.         m
  2464.         return None
  2465.  
  2466.     w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s%(zonediff)s' % {
  2467.         'year': m.group(1),
  2468.         'month': month,
  2469.         'day': day,
  2470.         'hour': hour,
  2471.         'minute': m.group(5),
  2472.         'zonediff': m.group(6) }
  2473.     if _debug:
  2474.         sys.stderr.write('Hungarian date parsed as: %s\n' % w3dtfdate)
  2475.     
  2476.     return _parse_date_w3dtf(w3dtfdate)
  2477.  
  2478. registerDateHandler(_parse_date_hungarian)
  2479.  
  2480. def _parse_date_w3dtf(dateString):
  2481.     
  2482.     def __extract_date(m):
  2483.         year = int(m.group('year'))
  2484.         if year < 100:
  2485.             year = 100 * int(time.gmtime()[0] / 100) + int(year)
  2486.         
  2487.         if year < 1000:
  2488.             return (0, 0, 0)
  2489.         julian = m.group('julian')
  2490.         if julian:
  2491.             julian = int(julian)
  2492.             month = julian / 30 + 1
  2493.             day = julian % 30 + 1
  2494.             jday = None
  2495.             while jday != julian:
  2496.                 t = time.mktime((year, month, day, 0, 0, 0, 0, 0, 0))
  2497.                 jday = time.gmtime(t)[-2]
  2498.                 diff = abs(jday - julian)
  2499.                 if jday > julian:
  2500.                     if diff < day:
  2501.                         day = day - diff
  2502.                     else:
  2503.                         month = month - 1
  2504.                         day = 31
  2505.                 diff < day
  2506.                 if jday < julian:
  2507.                     if day + diff < 28:
  2508.                         day = day + diff
  2509.                     else:
  2510.                         month = month + 1
  2511.                 day + diff < 28
  2512.                 continue
  2513.                 year < 1000
  2514.             return (year, month, day)
  2515.         month = m.group('month')
  2516.         day = 1
  2517.         if month is None:
  2518.             month = 1
  2519.         else:
  2520.             month = int(month)
  2521.             day = m.group('day')
  2522.             if day:
  2523.                 day = int(day)
  2524.             else:
  2525.                 day = 1
  2526.         return (year, month, day)
  2527.  
  2528.     
  2529.     def __extract_time(m):
  2530.         if not m:
  2531.             return (0, 0, 0)
  2532.         hours = m.group('hours')
  2533.         if not hours:
  2534.             return (0, 0, 0)
  2535.         hours = int(hours)
  2536.         minutes = int(m.group('minutes'))
  2537.         seconds = m.group('seconds')
  2538.         return (hours, minutes, seconds)
  2539.  
  2540.     
  2541.     def __extract_tzd(m):
  2542.         '''Return the Time Zone Designator as an offset in seconds from UTC.'''
  2543.         if not m:
  2544.             return 0
  2545.         tzd = m.group('tzd')
  2546.         if not tzd:
  2547.             return 0
  2548.         if tzd == 'Z':
  2549.             return 0
  2550.         hours = int(m.group('tzdhours'))
  2551.         minutes = m.group('tzdminutes')
  2552.         offset = (hours * 60 + minutes) * 60
  2553.         if tzd[0] == '+':
  2554.             return -offset
  2555.         return offset
  2556.  
  2557.     __date_re = '(?P<year>\\d\\d\\d\\d)(?:(?P<dsep>-|)(?:(?P<julian>\\d\\d\\d)|(?P<month>\\d\\d)(?:(?P=dsep)(?P<day>\\d\\d))?))?'
  2558.     __tzd_re = '(?P<tzd>[-+](?P<tzdhours>\\d\\d)(?::?(?P<tzdminutes>\\d\\d))|Z)'
  2559.     __tzd_rx = re.compile(__tzd_re)
  2560.     __time_re = '(?P<hours>\\d\\d)(?P<tsep>:|)(?P<minutes>\\d\\d)(?:(?P=tsep)(?P<seconds>\\d\\d(?:[.,]\\d+)?))?' + __tzd_re
  2561.     __datetime_re = '%s(?:T%s)?' % (__date_re, __time_re)
  2562.     __datetime_rx = re.compile(__datetime_re)
  2563.     m = __datetime_rx.match(dateString)
  2564.     if m is None or m.group() != dateString:
  2565.         return None
  2566.     gmt = __extract_date(m) + __extract_time(m) + (0, 0, 0)
  2567.     if gmt[0] == 0:
  2568.         return None
  2569.     return time.gmtime(time.mktime(gmt) + __extract_tzd(m) - time.timezone)
  2570.  
  2571. registerDateHandler(_parse_date_w3dtf)
  2572.  
  2573. def _parse_date_rfc822(dateString):
  2574.     '''Parse an RFC822, RFC1123, RFC2822, or asctime-style date'''
  2575.     data = dateString.split()
  2576.     if data[0][-1] in (',', '.') or data[0].lower() in rfc822._daynames:
  2577.         del data[0]
  2578.     
  2579.     if len(data) == 4:
  2580.         s = data[3]
  2581.         i = s.find('+')
  2582.         if i > 0:
  2583.             data[3:] = [
  2584.                 s[:i],
  2585.                 s[i + 1:]]
  2586.         else:
  2587.             data.append('')
  2588.         dateString = ' '.join(data)
  2589.     
  2590.     if len(data) < 5:
  2591.         dateString += ' 00:00:00 GMT'
  2592.     
  2593.     tm = rfc822.parsedate_tz(dateString)
  2594.     if tm:
  2595.         return time.gmtime(rfc822.mktime_tz(tm))
  2596.  
  2597. _additional_timezones = {
  2598.     'AT': -400,
  2599.     'ET': -500,
  2600.     'CT': -600,
  2601.     'MT': -700,
  2602.     'PT': -800 }
  2603. rfc822._timezones.update(_additional_timezones)
  2604. registerDateHandler(_parse_date_rfc822)
  2605.  
  2606. def _parse_date(dateString):
  2607.     '''Parses a variety of date formats into a 9-tuple in GMT'''
  2608.     for handler in _date_handlers:
  2609.         
  2610.         try:
  2611.             date9tuple = handler(dateString)
  2612.             if not date9tuple:
  2613.                 continue
  2614.             
  2615.             if len(date9tuple) != 9:
  2616.                 if _debug:
  2617.                     sys.stderr.write('date handler function must return 9-tuple\n')
  2618.                 
  2619.                 raise ValueError
  2620.             len(date9tuple) != 9
  2621.             map(int, date9tuple)
  2622.             return date9tuple
  2623.         continue
  2624.         except Exception:
  2625.             e = None
  2626.             if _debug:
  2627.                 sys.stderr.write('%s raised %s\n' % (handler.__name__, repr(e)))
  2628.             
  2629.             _debug
  2630.         
  2631.  
  2632.     
  2633.  
  2634.  
  2635. def _getCharacterEncoding(http_headers, xml_data):
  2636.     """Get the character encoding of the XML document
  2637.  
  2638.     http_headers is a dictionary
  2639.     xml_data is a raw string (not Unicode)
  2640.     
  2641.     This is so much trickier than it sounds, it's not even funny.
  2642.     According to RFC 3023 ('XML Media Types'), if the HTTP Content-Type
  2643.     is application/xml, application/*+xml,
  2644.     application/xml-external-parsed-entity, or application/xml-dtd,
  2645.     the encoding given in the charset parameter of the HTTP Content-Type
  2646.     takes precedence over the encoding given in the XML prefix within the
  2647.     document, and defaults to 'utf-8' if neither are specified.  But, if
  2648.     the HTTP Content-Type is text/xml, text/*+xml, or
  2649.     text/xml-external-parsed-entity, the encoding given in the XML prefix
  2650.     within the document is ALWAYS IGNORED and only the encoding given in
  2651.     the charset parameter of the HTTP Content-Type header should be
  2652.     respected, and it defaults to 'us-ascii' if not specified.
  2653.  
  2654.     Furthermore, discussion on the atom-syntax mailing list with the
  2655.     author of RFC 3023 leads me to the conclusion that any document
  2656.     served with a Content-Type of text/* and no charset parameter
  2657.     must be treated as us-ascii.  (We now do this.)  And also that it
  2658.     must always be flagged as non-well-formed.  (We now do this too.)
  2659.     
  2660.     If Content-Type is unspecified (input was local file or non-HTTP source)
  2661.     or unrecognized (server just got it totally wrong), then go by the
  2662.     encoding given in the XML prefix of the document and default to
  2663.     'iso-8859-1' as per the HTTP specification (RFC 2616).
  2664.     
  2665.     Then, assuming we didn't find a character encoding in the HTTP headers
  2666.     (and the HTTP Content-type allowed us to look in the body), we need
  2667.     to sniff the first few bytes of the XML data and try to determine
  2668.     whether the encoding is ASCII-compatible.  Section F of the XML
  2669.     specification shows the way here:
  2670.     http://www.w3.org/TR/REC-xml/#sec-guessing-no-ext-info
  2671.  
  2672.     If the sniffed encoding is not ASCII-compatible, we need to make it
  2673.     ASCII compatible so that we can sniff further into the XML declaration
  2674.     to find the encoding attribute, which will tell us the true encoding.
  2675.  
  2676.     Of course, none of this guarantees that we will be able to parse the
  2677.     feed in the declared character encoding (assuming it was declared
  2678.     correctly, which many are not).  CJKCodecs and iconv_codec help a lot;
  2679.     you should definitely install them if you can.
  2680.     http://cjkpython.i18n.org/
  2681.     """
  2682.     
  2683.     def _parseHTTPContentType(content_type):
  2684.         """takes HTTP Content-Type header and returns (content type, charset)
  2685.  
  2686.         If no charset is specified, returns (content type, '')
  2687.         If no content type is specified, returns ('', '')
  2688.         Both return parameters are guaranteed to be lowercase strings
  2689.         """
  2690.         if not content_type:
  2691.             pass
  2692.         content_type = ''
  2693.         (content_type, params) = cgi.parse_header(content_type)
  2694.         return (content_type, params.get('charset', '').replace("'", ''))
  2695.  
  2696.     sniffed_xml_encoding = ''
  2697.     xml_encoding = ''
  2698.     true_encoding = ''
  2699.     (http_content_type, http_encoding) = _parseHTTPContentType(http_headers.get('content-type'))
  2700.     
  2701.     try:
  2702.         if xml_data[:4] == 'Lo\xa7\x94':
  2703.             xml_data = _ebcdic_to_ascii(xml_data)
  2704.         elif xml_data[:4] == '\x00<\x00?':
  2705.             sniffed_xml_encoding = 'utf-16be'
  2706.             xml_data = unicode(xml_data, 'utf-16be').encode('utf-8')
  2707.         elif len(xml_data) >= 4 and xml_data[:2] == '\xfe\xff' and xml_data[2:4] != '\x00\x00':
  2708.             sniffed_xml_encoding = 'utf-16be'
  2709.             xml_data = unicode(xml_data[2:], 'utf-16be').encode('utf-8')
  2710.         elif xml_data[:4] == '<\x00?\x00':
  2711.             sniffed_xml_encoding = 'utf-16le'
  2712.             xml_data = unicode(xml_data, 'utf-16le').encode('utf-8')
  2713.         elif len(xml_data) >= 4 and xml_data[:2] == '\xff\xfe' and xml_data[2:4] != '\x00\x00':
  2714.             sniffed_xml_encoding = 'utf-16le'
  2715.             xml_data = unicode(xml_data[2:], 'utf-16le').encode('utf-8')
  2716.         elif xml_data[:4] == '\x00\x00\x00<':
  2717.             sniffed_xml_encoding = 'utf-32be'
  2718.             xml_data = unicode(xml_data, 'utf-32be').encode('utf-8')
  2719.         elif xml_data[:4] == '<\x00\x00\x00':
  2720.             sniffed_xml_encoding = 'utf-32le'
  2721.             xml_data = unicode(xml_data, 'utf-32le').encode('utf-8')
  2722.         elif xml_data[:4] == '\x00\x00\xfe\xff':
  2723.             sniffed_xml_encoding = 'utf-32be'
  2724.             xml_data = unicode(xml_data[4:], 'utf-32be').encode('utf-8')
  2725.         elif xml_data[:4] == '\xff\xfe\x00\x00':
  2726.             sniffed_xml_encoding = 'utf-32le'
  2727.             xml_data = unicode(xml_data[4:], 'utf-32le').encode('utf-8')
  2728.         elif xml_data[:3] == '\xef\xbb\xbf':
  2729.             sniffed_xml_encoding = 'utf-8'
  2730.             xml_data = unicode(xml_data[3:], 'utf-8').encode('utf-8')
  2731.         
  2732.         xml_encoding_match = re.compile('^<\\?.*encoding=[\'"](.*?)[\'"].*\\?>').match(xml_data)
  2733.     except:
  2734.         xml_encoding_match = None
  2735.  
  2736.     if xml_encoding_match:
  2737.         xml_encoding = xml_encoding_match.groups()[0].lower()
  2738.         if sniffed_xml_encoding and xml_encoding in ('iso-10646-ucs-2', 'ucs-2', 'csunicode', 'iso-10646-ucs-4', 'ucs-4', 'csucs4', 'utf-16', 'utf-32', 'utf_16', 'utf_32', 'utf16', 'u16'):
  2739.             xml_encoding = sniffed_xml_encoding
  2740.         
  2741.     
  2742.     acceptable_content_type = 0
  2743.     application_content_types = ('application/xml', 'application/xml-dtd', 'application/xml-external-parsed-entity')
  2744.     text_content_types = ('text/xml', 'text/xml-external-parsed-entity')
  2745.     if (http_content_type in application_content_types or http_content_type.startswith('application/')) and http_content_type.endswith('+xml'):
  2746.         acceptable_content_type = 1
  2747.         if not http_encoding and xml_encoding:
  2748.             pass
  2749.         true_encoding = 'utf-8'
  2750.     elif (http_content_type in text_content_types or http_content_type.startswith('text/')) and http_content_type.endswith('+xml'):
  2751.         acceptable_content_type = 1
  2752.         if not http_encoding:
  2753.             pass
  2754.         true_encoding = 'us-ascii'
  2755.     elif http_content_type.startswith('text/'):
  2756.         if not http_encoding:
  2757.             pass
  2758.         true_encoding = 'us-ascii'
  2759.     elif http_headers and not http_headers.has_key('content-type'):
  2760.         if not xml_encoding:
  2761.             pass
  2762.         true_encoding = 'iso-8859-1'
  2763.     elif not xml_encoding:
  2764.         pass
  2765.     true_encoding = 'utf-8'
  2766.     return (true_encoding, http_encoding, xml_encoding, sniffed_xml_encoding, acceptable_content_type)
  2767.  
  2768.  
  2769. def _toUTF8(data, encoding):
  2770.     '''Changes an XML data stream on the fly to specify a new encoding
  2771.  
  2772.     data is a raw sequence of bytes (not Unicode) that is presumed to be in %encoding already
  2773.     encoding is a string recognized by encodings.aliases
  2774.     '''
  2775.     if _debug:
  2776.         sys.stderr.write('entering _toUTF8, trying encoding %s\n' % encoding)
  2777.     
  2778.     if len(data) >= 4 and data[:2] == '\xfe\xff' and data[2:4] != '\x00\x00':
  2779.         if _debug:
  2780.             sys.stderr.write('stripping BOM\n')
  2781.             if encoding != 'utf-16be':
  2782.                 sys.stderr.write('trying utf-16be instead\n')
  2783.             
  2784.         
  2785.         encoding = 'utf-16be'
  2786.         data = data[2:]
  2787.     elif len(data) >= 4 and data[:2] == '\xff\xfe' and data[2:4] != '\x00\x00':
  2788.         if _debug:
  2789.             sys.stderr.write('stripping BOM\n')
  2790.             if encoding != 'utf-16le':
  2791.                 sys.stderr.write('trying utf-16le instead\n')
  2792.             
  2793.         
  2794.         encoding = 'utf-16le'
  2795.         data = data[2:]
  2796.     elif data[:3] == '\xef\xbb\xbf':
  2797.         if _debug:
  2798.             sys.stderr.write('stripping BOM\n')
  2799.             if encoding != 'utf-8':
  2800.                 sys.stderr.write('trying utf-8 instead\n')
  2801.             
  2802.         
  2803.         encoding = 'utf-8'
  2804.         data = data[3:]
  2805.     elif data[:4] == '\x00\x00\xfe\xff':
  2806.         if _debug:
  2807.             sys.stderr.write('stripping BOM\n')
  2808.             if encoding != 'utf-32be':
  2809.                 sys.stderr.write('trying utf-32be instead\n')
  2810.             
  2811.         
  2812.         encoding = 'utf-32be'
  2813.         data = data[4:]
  2814.     elif data[:4] == '\xff\xfe\x00\x00':
  2815.         if _debug:
  2816.             sys.stderr.write('stripping BOM\n')
  2817.             if encoding != 'utf-32le':
  2818.                 sys.stderr.write('trying utf-32le instead\n')
  2819.             
  2820.         
  2821.         encoding = 'utf-32le'
  2822.         data = data[4:]
  2823.     
  2824.     newdata = unicode(data, encoding)
  2825.     if _debug:
  2826.         sys.stderr.write('successfully converted %s data to unicode\n' % encoding)
  2827.     
  2828.     declmatch = re.compile('^<\\?xml[^>]*?>')
  2829.     newdecl = "<?xml version='1.0' encoding='utf-8'?>"
  2830.     if declmatch.search(newdata):
  2831.         newdata = declmatch.sub(newdecl, newdata)
  2832.     else:
  2833.         newdata = newdecl + u'\n' + newdata
  2834.     return newdata.encode('utf-8')
  2835.  
  2836.  
  2837. def _stripDoctype(data):
  2838.     """Strips DOCTYPE from XML document, returns (rss_version, stripped_data)
  2839.  
  2840.     rss_version may be 'rss091n' or None
  2841.     stripped_data is the same XML document, minus the DOCTYPE
  2842.     """
  2843.     entity_pattern = re.compile('<!ENTITY([^>]*?)>', re.MULTILINE)
  2844.     data = entity_pattern.sub('', data)
  2845.     doctype_pattern = re.compile('<!DOCTYPE([^>]*?)>', re.MULTILINE)
  2846.     doctype_results = doctype_pattern.findall(data)
  2847.     if not doctype_results or doctype_results[0]:
  2848.         pass
  2849.     doctype = ''
  2850.     if doctype.lower().count('netscape'):
  2851.         version = 'rss091n'
  2852.     else:
  2853.         version = None
  2854.     data = doctype_pattern.sub('', data)
  2855.     return (version, data)
  2856.  
  2857.  
  2858. def parse(url_file_stream_or_string, etag = None, modified = None, agent = None, referrer = None, handlers = []):
  2859.     '''Parse a feed from a URL, file, stream, or string'''
  2860.     result = FeedParserDict()
  2861.     result['feed'] = FeedParserDict()
  2862.     result['entries'] = []
  2863.     if _XML_AVAILABLE:
  2864.         result['bozo'] = 0
  2865.     
  2866.     if type(handlers) == types.InstanceType:
  2867.         handlers = [
  2868.             handlers]
  2869.     
  2870.     
  2871.     try:
  2872.         f = _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, handlers)
  2873.         data = f.read()
  2874.     except Exception:
  2875.         e = None
  2876.         result['bozo'] = 1
  2877.         result['bozo_exception'] = e
  2878.         data = ''
  2879.         f = None
  2880.  
  2881.     if f and data and hasattr(f, 'headers'):
  2882.         if gzip and f.headers.get('content-encoding', '') == 'gzip':
  2883.             
  2884.             try:
  2885.                 data = gzip.GzipFile(fileobj = _StringIO(data)).read()
  2886.             except Exception:
  2887.                 e = None
  2888.                 result['bozo'] = 1
  2889.                 result['bozo_exception'] = e
  2890.                 data = ''
  2891.             except:
  2892.                 None<EXCEPTION MATCH>Exception
  2893.             
  2894.  
  2895.         None<EXCEPTION MATCH>Exception
  2896.         if zlib and f.headers.get('content-encoding', '') == 'deflate':
  2897.             
  2898.             try:
  2899.                 data = zlib.decompress(data, -(zlib.MAX_WBITS))
  2900.             except Exception:
  2901.                 e = None
  2902.                 result['bozo'] = 1
  2903.                 result['bozo_exception'] = e
  2904.                 data = ''
  2905.             except:
  2906.                 None<EXCEPTION MATCH>Exception
  2907.             
  2908.  
  2909.         None<EXCEPTION MATCH>Exception
  2910.     
  2911.     if hasattr(f, 'info'):
  2912.         info = f.info()
  2913.         if info.has_key('Etag'):
  2914.             result['etag'] = info.getheader('ETag')
  2915.         
  2916.         last_modified = info.getheader('Last-Modified')
  2917.         if last_modified:
  2918.             result['modified'] = _parse_date(last_modified)
  2919.         
  2920.     
  2921.     if hasattr(f, 'url'):
  2922.         result['href'] = f.url
  2923.         result['status'] = 200
  2924.     
  2925.     if hasattr(f, 'status'):
  2926.         result['status'] = f.status
  2927.     
  2928.     if hasattr(f, 'headers'):
  2929.         result['headers'] = f.headers.dict
  2930.     
  2931.     if hasattr(f, 'close'):
  2932.         f.close()
  2933.     
  2934.     http_headers = result.get('headers', { })
  2935.     (result['encoding'], http_encoding, xml_encoding, sniffed_xml_encoding, acceptable_content_type) = _getCharacterEncoding(http_headers, data)
  2936.     if http_headers and not acceptable_content_type:
  2937.         if http_headers.has_key('content-type'):
  2938.             bozo_message = '%s is not an XML media type' % http_headers['content-type']
  2939.         else:
  2940.             bozo_message = 'no Content-type specified'
  2941.         result['bozo'] = 1
  2942.         result['bozo_exception'] = NonXMLContentType(bozo_message)
  2943.     
  2944.     (result['version'], data) = _stripDoctype(data)
  2945.     baseuri = http_headers.get('content-location', result.get('href'))
  2946.     baselang = http_headers.get('content-language', None)
  2947.     if result.get('status', 0) == 304:
  2948.         result['version'] = ''
  2949.         result['debug_message'] = 'The feed has not changed since you last checked, ' + 'so the server sent no data.  This is a feature, not a bug!'
  2950.         return result
  2951.     if not data:
  2952.         return result
  2953.     use_strict_parser = 0
  2954.     known_encoding = 0
  2955.     tried_encodings = []
  2956.     for proposed_encoding in (result['encoding'], xml_encoding, sniffed_xml_encoding):
  2957.         if proposed_encoding in tried_encodings:
  2958.             continue
  2959.         
  2960.         tried_encodings.append(proposed_encoding)
  2961.         
  2962.         try:
  2963.             data = _toUTF8(data, proposed_encoding)
  2964.             known_encoding = use_strict_parser = 1
  2965.         continue
  2966.         continue
  2967.  
  2968.     
  2969.     if not known_encoding and chardet:
  2970.         
  2971.         try:
  2972.             proposed_encoding = chardet.detect(data)['encoding']
  2973.             if proposed_encoding and proposed_encoding not in tried_encodings:
  2974.                 tried_encodings.append(proposed_encoding)
  2975.                 data = _toUTF8(data, proposed_encoding)
  2976.                 known_encoding = use_strict_parser = 1
  2977.  
  2978.     
  2979.     if not known_encoding and 'utf-8' not in tried_encodings:
  2980.         
  2981.         try:
  2982.             proposed_encoding = 'utf-8'
  2983.             tried_encodings.append(proposed_encoding)
  2984.             data = _toUTF8(data, proposed_encoding)
  2985.             known_encoding = use_strict_parser = 1
  2986.  
  2987.     
  2988.     if not known_encoding and 'windows-1252' not in tried_encodings:
  2989.         
  2990.         try:
  2991.             proposed_encoding = 'windows-1252'
  2992.             tried_encodings.append(proposed_encoding)
  2993.             data = _toUTF8(data, proposed_encoding)
  2994.             known_encoding = use_strict_parser = 1
  2995.  
  2996.     
  2997.     if not known_encoding:
  2998.         result['bozo'] = 1
  2999.         result['bozo_exception'] = CharacterEncodingUnknown('document encoding unknown, I tried ' + '%s, %s, utf-8, and windows-1252 but nothing worked' % (result['encoding'], xml_encoding))
  3000.         result['encoding'] = ''
  3001.     elif proposed_encoding != result['encoding']:
  3002.         result['bozo'] = 1
  3003.         result['bozo_exception'] = CharacterEncodingOverride('documented declared as %s, but parsed as %s' % (result['encoding'], proposed_encoding))
  3004.         result['encoding'] = proposed_encoding
  3005.     
  3006.     if not _XML_AVAILABLE:
  3007.         use_strict_parser = 0
  3008.     
  3009.     if use_strict_parser:
  3010.         feedparser = _StrictFeedParser(baseuri, baselang, 'utf-8')
  3011.         saxparser = xml.sax.make_parser(PREFERRED_XML_PARSERS)
  3012.         saxparser.setFeature(xml.sax.handler.feature_namespaces, 1)
  3013.         saxparser.setContentHandler(feedparser)
  3014.         saxparser.setErrorHandler(feedparser)
  3015.         source = xml.sax.xmlreader.InputSource()
  3016.         source.setByteStream(_StringIO(data))
  3017.         if hasattr(saxparser, '_ns_stack'):
  3018.             saxparser._ns_stack.append({
  3019.                 'http://www.w3.org/XML/1998/namespace': 'xml' })
  3020.         
  3021.         
  3022.         try:
  3023.             saxparser.parse(source)
  3024.         except Exception:
  3025.             e = None
  3026.             if _debug:
  3027.                 import traceback as traceback
  3028.                 traceback.print_stack()
  3029.                 traceback.print_exc()
  3030.                 sys.stderr.write('xml parsing failed\n')
  3031.             
  3032.             result['bozo'] = 1
  3033.             if not feedparser.exc:
  3034.                 pass
  3035.             result['bozo_exception'] = e
  3036.             use_strict_parser = 0
  3037.         except:
  3038.             None<EXCEPTION MATCH>Exception
  3039.         
  3040.  
  3041.     None<EXCEPTION MATCH>Exception
  3042.     if not use_strict_parser:
  3043.         if not known_encoding or 'utf-8':
  3044.             pass
  3045.         feedparser = _LooseFeedParser(baseuri, baselang, '')
  3046.         feedparser.feed(data)
  3047.     
  3048.     result['feed'] = feedparser.feeddata
  3049.     result['entries'] = feedparser.entries
  3050.     if not result['version']:
  3051.         pass
  3052.     result['version'] = feedparser.version
  3053.     result['namespaces'] = feedparser.namespacesInUse
  3054.     return result
  3055.  
  3056. if __name__ == '__main__':
  3057.     zopeCompatibilityHack()
  3058.     from pprint import pprint
  3059.     for url in urls:
  3060.         print url
  3061.         print 
  3062.         result = parse(url)
  3063.         pprint(result)
  3064.         print 
  3065.     
  3066.  
  3067.